////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.controls.listClasses
{

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;
import flash.utils.clearInterval;
import flash.utils.getTimer;
import flash.utils.setInterval;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IList;
import mx.collections.IViewCursor;
import mx.collections.ItemResponder;
import mx.collections.ListCollectionView;
import mx.collections.XMLListCollection;
import mx.collections.errors.CursorError;
import mx.collections.errors.ItemPendingError;
import mx.controls.dataGridClasses.DataGridListData;
import mx.core.DragSource;
import mx.core.EdgeMetrics;
import mx.core.EventPriority;
import mx.core.FlexSprite;
import mx.core.FlexShape;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IInvalidating;
import mx.core.IRawChildrenContainer;
import mx.core.IUIComponent;
import mx.core.ScrollControlBase;
import mx.core.ScrollPolicy;
import mx.core.SpriteAsset;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.effects.Tween;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.DragEvent;
import mx.events.FlexEvent;
import mx.events.ListEvent;
import mx.events.PropertyChangeEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.events.ScrollEventDirection;
import mx.events.TweenEvent;
import mx.managers.DragManager;
import mx.managers.IFocusManagerComponent;
import mx.skins.halo.ListDropIndicator;
import mx.styles.StyleManager;
import mx.utils.UIDUtil;

use namespace mx_internal;

//--------------------------------------
//  Events
//--------------------------------------

/**
 *  Dispatched when the <code>selectedIndex</code> or <code>selectedItem</code> property
 *  changes as a result of user interaction.
 *
 *  @eventType flash.events.event.CHANGE
 */
[Event(name="change", type="flash.events.Event")]

/**
 *  Dispatched when the <code>data</code> property changes.
 *
 *  <p>When you use a component as an item renderer,
 *  the <code>data</code> property contains the data to display.
 *  You can listen for this event and update the component
 *  when the <code>data</code> property changes.</p>
 * 
 *  @eventType mx.events.FlexEvent.DATA_CHANGE
 */
[Event(name="dataChange", type="mx.events.FlexEvent")]

/**
 *  Dispatched when the user rolls the mouse pointer over an item in the control.
 *
 *  @eventType mx.events.ListEvent.ITEM_ROLL_OVER
 */
[Event(name="itemRollOver", type="mx.events.ListEvent")]

/**
 *  Dispatched when the user rolls the mouse pointer out of an item in the control.
 *
 *  @eventType mx.events.ListEvent.ITEM_ROLL_OUT
 */
[Event(name="itemRollOut", type="mx.events.ListEvent")]

/**
 *  Dispatched when the user clicks on an item in the control.
 *
 *  @eventType mx.events.ListEvent.ITEM_CLICK
 */
[Event(name="itemClick", type="mx.events.ListEvent")]

/**
 *  Dispatched when the user double-clicks on an item in the control.
 *
 *  @eventType mx.events.ListEvent.ITEM_DOUBLE_CLICK
 */
[Event(name="itemDoubleClick", type="mx.events.ListEvent")]

//--------------------------------------
//  Styles
//--------------------------------------

include "../../styles/metadata/FocusStyles.as"
include "../../styles/metadata/PaddingStyles.as"

/**
 *  The colors to use for the backgrounds of the items in the list. 
 *  The value is an array of two or more colors. 
 *  The backgrounds of the list items alternate among the colors in the array. 
 *
 *  <p>For DataGrid controls, all items in a row have the same background color, 
 *  and each row's background color is determined from the array of colors.</p>
 *
 *  <p>For the TileList control, which uses a single list to populate a 
 *  two-dimensional display, the style can result in a checkerboard appearance,
 *  stripes, or other patterns based on the number of columns and rows and
 *  the number of colors specified.  TileList cycles through the colors, placing
 *  the individual item background colors according to the 
 *  layout direction. If you have an even number of colors and an even number of
 *  columns for a TileList layed out horizontally, you will get striping.  If
 *  the number of columns is an odd number, you will get a checkerboard pattern.
 *  </p>
 *
 *  <p>Only takes effect if no <code>backgroundColor</code> is specified.</p>
 *
 *  @default undefined
 */
[Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes")]

/**
 *  The skin to use to indicate where a dragged item can be dropped.
 *  When a ListBase-derived component is a potential drop target in a
 *  drag-and-drop operation, a call to the <code>showDropFeedback()</code>
 *  method makes an instance of this class and positions it one pixel above
 *  the itemRenderer for the item where, if the drop occurs, is the item after
 *  the dropped item.
 *
 *  @default mx.controls.listClasses.ListDropIndicator
 */
[Style(name="dropIndicatorSkin", type="Class", inherit="no")]

/**
 *  The number of pixels between the bottom of the row
 *  and the bottom of the renderer in the row.
 *
 *  @default 2
 */
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]

/**
 *  The number of pixels between the top of the row
 *  and the top of the renderer in the row.
 *  
 *  @default 2
 */
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]

/**
 *  The color of the background of a renderer when the user rolls over it.
 *
 *  @default 0xEEFEE6
 */
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes")]

/**
 *  The color of the background of a renderer when the user selects it.
 *
 *  @default 0x7FCEFF
 */
[Style(name="selectionColor", type="uint", format="Color", inherit="yes")]

/**
 *  The color of the background of a renderer when the component is disabled.
 *
 *  @default 0xDDDDDD
 */
[Style(name="selectionDisabledColor", type="uint", format="Color", inherit="yes")]

/**
 *  The duration of the selection effect.
 *  When an item is selected an effect plays as the background is colored.
 *  Set to 0 to disable the effect.
 *  
 *  @default 250
 *  
 */
[Style(name="selectionDuration", type="Number", format="Time", inherit="no")]

/**
 *  The easingFunction for the selection effect.
 *  When an item is selected an effect plays as the background is colored.
 *  The default is a linear fade in of the color.  An easingFunction can be used 
 *  for controlling the selection effect.
 *
 *  @default undefined
 */
[Style(name="selectionEasingFunction", type="Function", inherit="no")]

/**
 *  The color of the text of a renderer when the user rolls over a it.
 *
 *  @default 0x2B333C
 */
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]

/**
 *  The color of the text of a renderer when the user selects it.
 *
 *  @default 0x2B333C
 */
[Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")]

/**
 *  A flag that controls whether items are highlighted as the mouse rolls 
 *  over them.
 *  If <code>true</code>, rows are highlighted as the mouse rolls over them.
 *  If <code>false</code>, rows are highlighted only when selected.
 *
 *  @default true
 */
[Style(name="useRollOver", type="Boolean", inherit="no")]

/**
 *  The vertical alignment of a renderer in a row.
 *  Possible values are <code>"top"</code>, <code>"middle"</code>,
 *  and <code>"bottom"</code>.
 *  The DataGrid positions the renderers in a row based on this style
 *  and the <code>paddingTop</code> and <code>paddingBottom</code> styles.
 *  if the item in the columns for a row have different heights
 *  Other list classes do not use <code>verticalAlign</code> but
 *  the item renderers can examine this style property
 *  and adjust their layout based on it.
 *
 *  @default "top"
 */
[Style(name="verticalAlign", type="String", enumeration="bottom,middle,top", inherit="no")]

//--------------------------------------
//  Other metadata
//--------------------------------------

[AccessibilityClass(implementation="mx.accessibility.ListBaseAccImpl")]

/**
 *  The ListBase class is the base class for controls that represent lists
 *  of items that can have one or more selected and can scroll through the
 *  items.  Items are supplied using the <code>dataProvider</code> property
 *  and displayed via item renderers.
 *
 *  <p>In a model/view architecture, the ListBase-derived class represents
 *  the view, and the dataProvider object represents the model.</p>
 *
 *  @mxml
 *  
 *  <p>The ListBase class inherits all of the tag properties of its superclasses,
 *  and adds the following tag properties:</p>
 *  
 *  <pre>
 *  &lt;mx:<i>tagname</i>
 *    <b>Properties</b>
 *    allowDragSelection="false|true"
 *    allowMultipleSelection="false|true"
 *    columnCount="4"
 *    columnWidth="NaN"
 *    dataProvider="null"
 *    dataTipField="label"
 *    dataTipFunction="null"
 *    dragEnabled="false|true"
 *    dragMoveEnabled="false|true"
 *    dropEnabled="false|true"
 *    iconField="null"
 *    iconFunction="null"
 *    itemRenderer="null"
 *    labelField="label"
 *    labelFunction="null"
 *    lockedColumnCount=0
 *    lockedRowCount=0
 *    menuSelectionMode="false|true"
 *    rowCount="-1"
 *    rowHeight="NaN"
 *    selectable="true|false"
 *    selectedIndex="-1"
 *    selectedIndices="null"
 *    selectedItem="null"
 *    selectedItems="null"
 *    showDataTips="false|true"
 *    variableRowHeight="false|true"
 *    wordWrap="false|true"<br></br>
 *    <b>Styles</b>
 *    alternatingItemColors="undefined"
 *    dropIndicatorSkin="ListDropIndicator"
 *    focusAlpha="0.5"
 *    focusRoundedCorners="tl tr bl br"
 *    paddingBottom="2"
 *    paddingLeft="2"
 *    paddingRight="0"
 *    paddingTop="2"
 *    rollOverColor="0xEEFEE6"
 *    selectionColor="0x7FCEFF"
 *    selectionDisabledColor="0xDDDDDD"
 *    selectionDuration="250"
 *    selectionEasingFunction="undefined"
 *    textRollOverColor="0x2B333C"
 *    textSelectedColor="0x2B333C"
 *    useRollOver="true|false"
 *    verticalAlign="top|middle|bottom"<br></br>
 *    <b>Events</b>
 *    change="<i>No default</i>"
 *    dataChange="<i>No default</i>"
 *    itemClick="<i>No default</i>"
 *    itemDoubleClick="<i>No default</i>"
 *    itemRollOut="<i>No default</i>"
 *    itemRollOver="<i>No default</i>"
 *    itemClick="<i>No default</i>"
 *   /&gt;
 *  </pre>
 *
 *  @see mx.collections.ICollectionView
 */
public class ListBase extends ScrollControlBase
					  implements IDataRenderer, IFocusManagerComponent,
					  IListItemRenderer, IDropInListItemRenderer
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class constants
	//
	//--------------------------------------------------------------------------

    /**
	 *  @private
	 *  Anything in this list of styles will trigger a full repaint.
	 */
    private var IS_ITEM_STYLE:Object =
	{
		alternatingItemColors: true,
		backgroundColor: true,
        backgroundDisabledColor: true,
		color: true,
        rollOverColor: true,
		selectionColor: true,
		selectionDisabledColor: true,
        styleName: true,
		textColor:true,
        textRollOverColor: true,
		textSelectedColor: true
    };

	/**
	 *  @private
	 *  Mouse movement threshold for determining when to start a drag.
	 */
	mx_internal static const DRAG_THRESHOLD:int = 4;

	//--------------------------------------------------------------------------
	//
	//  Class mixins
	//
	//--------------------------------------------------------------------------

    /**
	 *  @private
	 *  Placeholder for mixin by ListBaseAccImpl.
     */
    mx_internal static var createAccessibilityImplementation:Function;

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

    /**
     *  Constructor.
     */
    public function ListBase()
    {
        super();

        tabEnabled = true;

        addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
        addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
        addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
        addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
        addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
        addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
        addEventListener(MouseEvent.CLICK, mouseClickHandler);
		addEventListener(MouseEvent.DOUBLE_CLICK, mouseDoubleClickHandler);

        invalidateProperties();
    }

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

    /**
	 *  An ICollectionView that represents the data provider.
	 *  When you set the <code>dataProvider</code> property,
	 *  Flex wraps the data provider as necessary to 
	 *  support the ICollectionView interface and 
	 *  sets this property to the result.
	 *  The ListBase class then uses this property to access
	 *  data in the provider.
	 *  When you  get the <code>dataProvider</code> property, 
	 *  Flex returns this value.  
     */
    protected var collection:ICollectionView;

    /**
	 *  The main IViewCursor used to fetch items from the
	 *  data provider and pass the items to the renderers.
	 *  At the end of any sequence of code, it must always
	 *  be positioned at the topmost visible item being displayed.
     */
    protected var iterator:IViewCursor;

    /**
	 *  A flag that indicates that a page fault as occurred and that
	 *  the iterator's position is not valid (not positioned at the topmost
	 *  item being displayed).
	 *  If the component gets a page fault (an ItemPending error), 
	 *  it sets <code>iteratorValid</code> to <code>false</code>.  Code that
	 *  normally handles the rendering of items checks this flag and does not 
	 *  run until the page of data comes in from the server.
     */
    protected var iteratorValid:Boolean = true;

    /**
	 *  The most recent seek that caused a page fault.
	 *  If there are multiple page faults, only the most recent one
	 *  is of interest, as that is where to position the iterator 
	 *  and start rendering rows again.
     */
    protected var lastSeekPending:ListBaseSeekPending;

    /**
	 *  A hash table of data provider item renderers currently in view.
	 *  The table is indexed by the data provider item's UID and is used
	 *  to quickly get the renderer used to display a partuclar item.
     */
    protected var visibleData:Object = {};

	/**
	 *  An internal display object that parents all of the item renderers,
	 *  selection and highlighting indicators and other supporting graphics.
	 *  This is roughly equivalent to the <code>contentPane</code> in the 
	 *  Container class, and is used for managing scrolling.
     */
    protected var listContent:ListBaseContentHolder;

    /**
	 *  The layer in <code>listContent</code> where all selection 
	 *  and highlight indicators are drawn.
     */
    protected var selectionLayer:Sprite;

    /**
	 *  An Array of Arrays that contains
	 *  the itemRenderer instances that render each data provider item.
	 *  This is a two-dimensional row major array
	 *  (array of rows that are arrays of columns).
     */
    protected var listItems:Array = [];
    
    /**
	 *  An array of ListRowInfo objects that cache row heights and 
	 *  other tracking information for the rows in listItems.
     */
    protected var rowInfo:Array = [];

    /**
	 *  A hash map of item renderers to their respective ListRowInfo object.
	 *  The ListRowInfo object is indexed by the DisplayObject name of the
	 *  item renderer.
     */
    protected var rowMap:Object = {};

    /**
	 *  A stack of unused item renderers.
     *  Most list classes recycle renderers they've already created
	 *  as they scroll out of the displayable area; doing so 
	 *  saves time during scrolling.
	 *  The recycled renderers are stored here.
     */
    protected var freeItemRenderers:Array = [];

    /**
	 *  The UID of the item that is current rolled over or under the caret.
     */
    protected var highlightUID:String;

    /**
	 *  The renderer that is currently rolled over or under the caret.
     */
    protected var highlightItemRenderer:IListItemRenderer;

    /**
	 *  The DisplayObject that contains the graphics that indicates
	 *  which renderer is highlighted.
     */
    protected var highlightIndicator:Sprite;

    /**
	 *  The UID of the item under the caret.
     */
    protected var caretUID:String;

    /**
	 *  The renderer for the item under the caret.  In the selection
	 *  model, there is an anchor, a caret and a highlighted item.  When
	 *  the mouse is being used for selection, the item under the mouse is
	 *  highlighted as the mouse rolls over the item.  
	 *  When the mouse is clicked with no modifier keys (SHIFT or CTRL), the
	 *  set of selected items is cleared and the item under the highlight is
	 *  selected and becomes the anchor.  The caret is unused in mouse
	 *  selection.  If there is an anchor and another item is selected while
	 *  using the SHIFT key, the old set of selected items is cleared, and
	 *  all items between the item and the anchor are selected.  Clicking
	 *  items while using the CTRL key toggles the selection of individual
	 *  items and does not move the anchor.
	 *
	 *  <p>When selecting items using the keyboard, if the arrow keys are used
	 *  with no modifier keys, the old selection is cleared and the new item
	 *  is selected and becomes the anchor and the caret, and a caret indicator
	 *  is shown around the selection highlight.  If the user uses arrow keys
	 *  with the SHIFT key, the old selection is cleared and the items between
	 *  the anchor and the new item are selected.  The caret moves to the new
	 *  item.  If arrow keys are used with the CTRL key, just the caret moves.
	 *  The user can use the SPACE key to toggle selection of the item under
	 *  the caret.</p>
	 */
    protected var caretItemRenderer:IListItemRenderer;

    /**
	 *  The DisplayObject that contains the graphics that indicate
	 *  which renderer is the caret.
     */
    protected var caretIndicator:Sprite;

    /**
	 *  A hash table of ListBaseSelectionData objects that track which
	 *  items are currently selected.  The table is indexed by the UID
	 *  of the items.
	 *
	 *  @see mx.controls.listClasses.ListBaseSelectionData
     */
    protected var selectedData:Object = {};

    /**
	 *  A hash table of selection indicators.  This table allows the component
	 *  to quickly find and remove the indicators when the set of selected
	 *  items is cleared.  The table is indexed by the item's UID.
     */
    protected var selectionIndicators:Object = {};

    /**
     *  A hash table of selection tweens.  This allows the component to
	 *  quickly find and clean up any tweens in progress if the set
	 *  of selected items is cleared.  The table is indexed by the item's UID.
     */
    protected var selectionTweens:Object = {};

    /**
	 *  A bookmark to the item under the caret.  A bookmark allows the
	 *  component to quickly seek to a position in the collection of items.
     */
    protected var caretBookmark:CursorBookmark;

    /**
	 *  A bookmark to the item that is the anchor.  A bookmark allows the
	 *  component to quickly seek to a position in the collection of items.
	 *  This property is used when selecting a set of items between the anchor
	 *  and the caret or highlighted item, and when finding the selected item
	 *  after a Sort or Filter is applied.
     */
    protected var anchorBookmark:CursorBookmark;

    /**
	 *  A flag that indicates whether to show caret.  
	 *  This property is usually set
	 *  to <code>false</code> when mouse activity is detected and set back to 
	 *  <code>true</code> when the keyboard is used for selection.
     */
    protected var showCaret:Boolean;

    /**
     *  The most recently calculated index where the drag item
	 *  should be added to the drop target.
     */
    protected var lastDropIndex:int;

	/**
	 *  A flag that indicates whether the <code>columnWidth</code> 
	 *  and <code>rowHeight</code> properties need to be calculated.
	 *  This property is set if a style changes that can affect the
	 *  measurements of the renderer, or if the data provider is changed.
	 */
	protected var itemsNeedMeasurement:Boolean = true;

    /**
     *  A flag that indicates that the size of the renderers may have changed.
	 *  The component usually responds by re-applying the data items to all of
	 *  the renderers on the next <code>updateDisplayList()</code> call.
	 *  There is an assumption that re-applying the items will invalidate the
	 *  item renderers and cause them to re-measure.
     */
    protected var itemsSizeChanged:Boolean = false;

    /**
     *  A flag that indicates that the renderer changed.
	 *  The component usually responds by destroying all existing renderers
	 *  and redrawing all of the renderers on the next 
	 *  <code>updateDisplayList()</code> call.
     */
    protected var rendererChanged:Boolean = false;

    /**
     *  A flag that indicates whether the value of the <code>wordWrap</code> 
	 *  property has changed since the last time the display list was updated.
	 *  This property is set when you change the <code>wordWrap</code> 
	 *  property value, and is reset 
	 *  to <code>false</code> by the <code>updateDisplayList()</code> method.
	 *  The component usually responds by re-applying the data items to all of
	 *  the renderers on the next <code>updateDisplayList()</code> call.
	 *  This is different from itemsSizeChanged because it further indicates
	 *  that re-applying the data items to the renderers may not invalidate them
	 *  since the only thing that changed was whether or not the renderer should
	 *  factor in wordWrap into its size calculations
     */
    protected var wordWrapChanged:Boolean = false;

    /**
     *  A flag that indicates if keyboard selection was interrupted by 
	 *  a page fault.  The component responds by suspending the rendering
	 *  of items until the page of data arrives.
	 *  The <code>finishKeySelection()</code> method will be called
	 *  when the paged data arrives
     */
    protected var keySelectionPending:Boolean = false;

	/**
     *  @private
	 *  Cached style value.
     */
	mx_internal var cachedPaddingTop:Number;
    
	/**
     *  @private
	 *  Cached style value.
     */
    mx_internal var cachedPaddingBottom:Number;
    
	/**
     *  @private
	 *  Cached style value.
     */
    mx_internal var cachedVerticalAlign:String;
    
    /**
	 *  @private
	 */
	private var oldUnscaledWidth:Number;
    
    /**
	 *  @private
	 */
	private var oldUnscaledHeight:Number;

    /**
	 *  @private
	 */
    private var horizontalScrollPositionPending:Number;

    /**
	 *  @private
	 */
    private var verticalScrollPositionPending:Number;

    /**
	 *  @private
	 */
    private var mouseDownPoint:Point;

    /**
	 *  @private
	 */
    private var bSortItemPending:Boolean = false;

	// these three keep track of the key selection that caused
	// the page fault
	private var bShiftKey:Boolean = false;
	private var bCtrlKey:Boolean = false;
	private var lastKey:uint = 0;
	private var bSelectItem:Boolean = false;

	/**
	 *  @private
	 *  true if we don't know for sure what index we're on in the database
	 */
    private var approximate:Boolean = false;

    // if false, pixel scrolling only in horizontal direction
    mx_internal var bColumnScrolling:Boolean = true;

	// either "horizontal", "vertical", "grid"  Used to determine how
	// to measure the list.
    mx_internal var listType:String = "grid";

    private var bSelectOnRelease:Boolean;
    private var mouseDownItem:IListItemRenderer;

    private var bSelectionChanged:Boolean = false;
    private var bSelectedIndexChanged:Boolean = false;
    private var bSelectedItemChanged:Boolean = false;
    private var bSelectedItemsChanged:Boolean = false;
    private var bSelectedIndicesChanged:Boolean = false;

	/**
     *  @private
	 *  Dirty flag for the cache style value cachedPaddingTop.
     */
    private var cachedPaddingTopInvalid:Boolean = true;
    
	/**
     *  @private
	 *  Dirty flag for the cache style value cachedPaddingBottom.
     */
    private var cachedPaddingBottomInvalid:Boolean = true;
    
	/**
     *  @private
	 *  Dirty flag for the cache style value cachedVerticalAlign.
     */
    private var cachedVerticalAlignInvalid:Boolean = true;

    /**
     *  @private
     *  The first ListBaseSelectionData in a link list of ListBaseSelectionData.
     *  This represents the item that was most recently selected.  
     *  ListBaseSelectionData instances are linked together and keep track of the 
     *  order the user selects an item.  This order is reflected in selectedIndices 
     *  and selectedItems.
     */
    private var firstSelectionData:ListBaseSelectionData;

    /**
	 *  The renderer that is or was rolled over or under the caret.
     */
    mx_internal var lastHighlightItemRenderer:IListItemRenderer;

    private var dragScrollingInterval:int = 0;

	/**
	 *  @private
	 *  An Array of Shapes that are used as clip masks for the list items
     */
	private var itemMaskFreeList:Array;

    /**
     *  @private
     *  Whether the mouse button is pressed
     */
    mx_internal var isPressed:Boolean = false;

    /**
	 *  A separate IViewCursor used to find indices of items and
	 *  other things.  The collectionIterator can be at any
	 *  place within the set of items.
     */
    mx_internal var collectionIterator:IViewCursor;

    mx_internal var dropIndicator:IFlexDisplayObject;

 	//--------------------------------------------------------------------------
	//
	//  Overridden properties: UIComponent
	//
	//--------------------------------------------------------------------------

	//----------------------------------
    //  enabled
    //----------------------------------

	[Inspectable(category="General")]

    /**
     *  @private
     */
    override public function set enabled(value:Boolean):void
    {
        super.enabled = value;

        var ui:IFlexDisplayObject = border as IFlexDisplayObject;
        if (ui)
        {
			if (ui is IUIComponent)
				IUIComponent(ui).enabled = value;
            if (ui is IInvalidating)
				IInvalidating(ui).invalidateDisplayList();
        }

        itemsSizeChanged = true;

        invalidateDisplayList();
    }

	//--------------------------------------------------------------------------
	//
	//  Overridden properties: ScrollControlBase
	//
	//--------------------------------------------------------------------------

	//----------------------------------
    //  horizontalScrollPolicy
    //----------------------------------

    /**
     *  @private
     */
    override public function set horizontalScrollPolicy(value:String):void
    {
        super.horizontalScrollPolicy = value;
        itemsSizeChanged = true;

        invalidateDisplayList();
    }

	//----------------------------------
    //  horizontalScrollPosition
    //----------------------------------

    /**
     *  @private
     */
    override public function get horizontalScrollPosition():Number
    {
        if (!isNaN(horizontalScrollPositionPending))
            return horizontalScrollPositionPending;

        return super.horizontalScrollPosition;
    }

    /**
     *  @private
     */
    override public function set horizontalScrollPosition(value:Number):void
    {
        // if not init or no data;
        if (listItems.length == 0 || !dataProvider || !isNaN(horizontalScrollPositionPending))
        {
            horizontalScrollPositionPending = value;
            if (dataProvider)
                invalidateDisplayList();
            return;
        }
        horizontalScrollPositionPending = NaN;

        // trace("set horizontalScrollPosition " + value + " " + super.horizontalScrollPosition);

        var oldValue:int = super.horizontalScrollPosition;
        super.horizontalScrollPosition = value;

		removeClipMask();

        if (oldValue != value)
        {
			// we're going to get a full repaint soon so don't bother here.
			if (itemsSizeChanged)
				return;

            var deltaPos:int = value - oldValue;
            var direction:Boolean = (deltaPos > 0);
            deltaPos = Math.abs(deltaPos);
            if (bColumnScrolling && deltaPos >= columnCount - lockedColumnCount)
            {
				var index:int = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
                 //if we scrolled more than the number of scrollable rows
				try
				{
					iterator.seek(CursorBookmark.FIRST, index);
				}
				catch (e:ItemPendingError)
				{
					// trace("IPE in set horizontalScrollPosition");
					lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, index)
					e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
														lastSeekPending));
					iteratorValid = false;
					// do nothing, we'll repaint when the data arrives
				}
                var cursorPos:CursorBookmark = iterator.bookmark;
                clearIndicators();
                visibleData = {};
                makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
                iterator.seek(cursorPos, 0);
                drawRowBackgrounds();
            }
            else
            {
                scrollHorizontally(value, deltaPos, direction);
            }
        }

		addClipMask(false);
    }

	//----------------------------------
    //  verticalScrollPolicy
    //----------------------------------

    /**
     *  @private
     */
    override public function set verticalScrollPolicy(value:String):void
    {
        super.verticalScrollPolicy = value;
        itemsSizeChanged = true;

        invalidateDisplayList();
    }

	//----------------------------------
    //  verticalScrollPosition
    //----------------------------------

    /**
     *  @private
     */
    override public function get verticalScrollPosition():Number
    {
        if (!isNaN(verticalScrollPositionPending))
            return verticalScrollPositionPending;

        return super.verticalScrollPosition;
    }

    /**
     *  @private
     */
    override public function set verticalScrollPosition(value:Number):void
    {
        if (listItems.length == 0 || !dataProvider || !isNaN(verticalScrollPositionPending))
        {
            verticalScrollPositionPending = value;
            if (dataProvider)
                invalidateDisplayList();
            return;
        }
        verticalScrollPositionPending = NaN;

        var oldValue:int = super.verticalScrollPosition;
        super.verticalScrollPosition = value;

        removeClipMask();

		// trace("set verticalScrollPosition", oldValue, value);
        if (oldValue != value)
        {
            var deltaPos:int = value - oldValue;
            var direction:Boolean = (deltaPos > 0);
            deltaPos = Math.abs(deltaPos);
            if (deltaPos >= rowInfo.length - lockedRowCount || !iteratorValid)
            {
				var index:int = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
                 //if we scrolled more than the number of scrollable rows
				try
				{
					iterator.seek(CursorBookmark.FIRST, index);
				}
				catch (e:ItemPendingError)
				{
					// trace("IPE in set verticalScrollPosition");
					lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, index)
					e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
														lastSeekPending));
					iteratorValid = false;
					// do nothing, we'll repaint when the data arrives
				}
                var cursorPos:CursorBookmark = iterator.bookmark;
                clearIndicators();
                visibleData = {};
                makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
                iterator.seek(cursorPos, 0);
            }
            else
            {
                scrollVertically(value, deltaPos, direction);
            }
			// if variable rowheight, we have to recalibrate the scrollbars thumb size
			// on each scroll, otherwise you can't scroll down to a bunch of fat rows
			// at the bottom of a list.
			if (variableRowHeight)
				configureScrollBars();

            drawRowBackgrounds();
        }
        // if needed, add a clip mask to the items in the last row of the list
        addClipMask(false);
    }

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
    //  allowDragSelection
    //----------------------------------

	/**
     *  A flag that indicates whether drag-selection is enabled.
	 *  Drag-Selection is the ability to select an item by dragging
	 *  into it as opposed to normal selection where you can't have
	 *  the mouse button down when you mouse over the item you want
	 *  to select.  This feature is used in ComboBox dropdowns
	 *  to support pressing the mouse button when the mouse is over the 
	 *  dropdown button then dragging the mouse into the dropdown to select
	 *  an item.
	 *
	 *  @default false
     */
	public var allowDragSelection:Boolean = false;

	//----------------------------------
    //  allowMultipleSelection
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the allowMultipleSelection property.
	 */
	private var _allowMultipleSelection:Boolean = false;

	[Inspectable(category="General", enumeration="false,true", defaultValue="false")]

    /**
	 *  A flag that indicates whether you can allow more than one item to be
	 *  selected at the same time.
     *  If <code>true</code>, users can select multiple items.
     *  There is no option to disallow discontiguous selection.
     *  Standard complex selection options are always in effect 
	 *  (shift-click, control-click).
	 *
     *  @default false
     */
    public function get allowMultipleSelection():Boolean
    {
        return _allowMultipleSelection;
    }

    /**
	 *  @private
	 */
    public function set allowMultipleSelection(value:Boolean):void
    {
        _allowMultipleSelection = value;
    }

	//----------------------------------
    //  anchorIndex
    //----------------------------------

	/**
	 *  The offset of the item in the data provider that is the selection
	 *  anchor point.
	 *
	 *  @see mx.controls.listClasses.ListBase#caretItemRenderer
     */
    protected var anchorIndex:int = -1;

	//----------------------------------
    //  caretIndex
    //----------------------------------

	/**
	 *  The offset of the item in the data provider that is the selection
	 *  caret point.
	 *
	 *  @see mx.controls.listClasses.ListBase#caretItemRenderer
     */
    protected var caretIndex:int = -1;

	//----------------------------------
    //  columnCount
    //----------------------------------

    /**
     *  @private
	 *  Storage for the columnCount property.
     */
    private var _columnCount:int = -1;
    
    /**
     *  @private
     */
	private var columnCountChanged:Boolean = true;

	/**
     *  The number of columns to be displayed in a TileList control or items 
     *  in a HorizontalList control.
	 *  For the DataGrid it is the number of visible columns.
     *  <b>Note</b>: Setting this property has no effect on a DataGrid control,
     *  which bases the number of columns on the control width and the
     *  individual column widths.
	 * 
	 *  @default 4
     */
    public function get columnCount():int
    {
        return _columnCount;
    }

    /**
     *  @private
     */
    public function set columnCount(value:int):void
    {
        explicitColumnCount = value;

        if (_columnCount != value)
        {
			setColumnCount(value);
			columnCountChanged = true;
			invalidateProperties();

            invalidateSize();
            itemsSizeChanged = true;
            invalidateDisplayList();

            dispatchEvent(new Event("columnCountChanged"));
        }
    }

    /**
     *  Internal version for setting columnCount
	 *  without invalidation or notification.
     */
    mx_internal function setColumnCount(value:int):void
    {
		_columnCount = value;
    }

	//----------------------------------
    //  columnWidth
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the columnWidth property.
	 */
	private var _columnWidth:Number;
    
    /**
	 *  @private
	 */
	private var columnWidthChanged:Boolean = false;

	/**
     *  The width of the control's columns.
     *  This property is used by TileList and HorizontalList controls;
     *  It has no effect on DataGrid controls, where you set the individual
     *  DataGridColumn widths.
     *  
	 * @default 50
     */
    public function get columnWidth():Number
    {
        return _columnWidth;
    }

    /**
     *  @private
     */
    public function set columnWidth(value:Number):void
    {
        explicitColumnWidth = value;

        if (_columnWidth != value)
        {
            setColumnWidth(value);

            invalidateSize();
            itemsSizeChanged = true;
            invalidateDisplayList();

            dispatchEvent(new Event("columnWidthChanged"));
        }
    }

    /**
     *  Internal version of setting columnWidth
	 *  without invalidation or notification.
     */
    mx_internal function setColumnWidth(value:Number):void
    {
        _columnWidth = value;
    }

    //----------------------------------
    //  data
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the data property.
	 */
	private var _data:Object;

	[Bindable("dataChange")]
    [Inspectable(environment="none")]

	/**
	 *  The item in the data provider this component should render when
	 *  this component is used as an item renderer or item editor.
	 *  The list class sets this property on each renderer or editor
	 *  and the component displays the data.  ListBase-derived classes
	 *  support this property for complex situations like having a
	 *  List of DataGrids or a DataGrid where one column is a List.
     *
	 *  <p>The list classes use the <code>listData</code> property
	 *  in addition to the <code>data</code> property to determine what
	 *  to display.
	 *  If the list class is in a DataGrid it expects the <code>dataField</code>
	 *  property of the column to map to a property in the data
	 *  and sets <code>selectedItem</code> value to that property.
	 *  If it is in a List or TileList control, it expects the 
	 *  <code>labelField</code> property of the list to map to a property 
	 *  in the data, and sets <code>selectedItem</code> value to that property.
	 *  Otherwise it sets the <code>selectedItem</code> to the data itself.</p>
	 * 
	 *  <p>This property uses the data provider but does not set it. 
	 *  In all cases, you must set the data provider in some other way.</p>
	 *
     *  <p>You do not set this property in MXML.</p>
     *
     *  @see mx.core.IDataRenderer
	 */
    public function get data():Object
    {
        return _data;
    }

    /**
	 *  @private
	 */
    public function set data(value:Object):void
    {
        _data = value;

        if (_listData && _listData is DataGridListData)
            selectedItem = _data[DataGridListData(_listData).dataField];
        else if (_listData is ListData && ListData(_listData).labelField in _data)
			selectedItem = _data[ListData(_listData).labelField];
		else
            selectedItem = _data;

		dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    }

	//----------------------------------
    //  dataProvider
    //----------------------------------

    [Bindable("collectionChange")]
    [Inspectable(category="Data", defaultValue="undefined")]

	/**
     *  Set of data to be viewed.
     *  This property lets you use most types of objects as data providers.
	 *  If you set the <code>dataProvider</code> property to an Array, 
	 *  it will be converted to an ArrayCollection. If you set the property to
	 *  an XML object, it will be converted into an XMLListCollection with
	 *  only one item. If you set the property to an XMLList, it will be 
	 *  converted to an XMLListCollection.  
	 *  If you set the property to an object that implements the 
	 *  IList or ICollectionView interface, the object will be used directly.
	 *
	 *  <p>As a consequence of the conversions, when you get the 
	 *  <code>dataProvider</code> property, it will always be
	 *  an ICollectionView, and therefore not necessarily be the type of object
	 *  you used to  you set the property.
	 *  This behavor is important to understand if you want to modify the data 
	 *  in the data provider: changes to the original data may not be detected, 
	 *  but changes to the ICollectionView object that you get back from the 
	 *  <code>dataProvider</code> property will be detected.</p>
     * 
     *  @default null
     *  @see mx.collections.ICollectionView
     */
    public function get dataProvider():Object
    {
        return collection;
    }

    /**
	 *  @private
	 */
	public function set dataProvider(value:Object):void
    {
        if (collection)
        {
            collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
        }

        if (value is Array)
        {
            collection = new ArrayCollection(value as Array);
        }
        else if (value is ICollectionView)
        {
            collection = ICollectionView(value);
        }
        else if (value is IList)
        {
            collection = new ListCollectionView(IList(value));
        }
		else if (value is XMLList)
		{
			collection = new XMLListCollection(value as XMLList);
		}
		else if (value is XML)
		{
			var xl:XMLList = new XMLList();
			xl += value;
			collection = new XMLListCollection(xl);
		}
        else
        {
            // convert it to an array containing this one item
            var tmp:Array = [];
			if (value != null)
				tmp.push(value);
            collection = new ArrayCollection(tmp);
        }
        // get an iterator for the displaying rows.  The CollectionView's
        // main iterator is left unchanged so folks can use old DataSelector
        // methods if they want to
        iterator = collection.createCursor();
        collectionIterator = collection.createCursor(); //IViewCursor(collection);

        // trace("ListBase added change listener");
        collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);

        clearSelectionData();

		var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
		event.kind = CollectionEventKind.RESET;
        collectionChangeHandler(event);
        dispatchEvent(event);

		itemsNeedMeasurement = true;
		invalidateProperties();
        invalidateSize();
        invalidateDisplayList();
    }

    //----------------------------------
    //  dataTipField
    //----------------------------------

    /**
     *  @private
     *  Storage for the dataTipField property.
     */
    private var _dataTipField:String = "label";

    [Bindable("dataTipFieldChanged")]
    [Inspectable(category="Data", defaultValue="label")]

    /**
     *  Name of the field in the data provider items to display as the 
	 *  data tip. By default, the list looks for a property named 
	 *  <code>label</code> on each item and displays it.
     *  However, if the data objects do not contain a <code>label</code> 
	 *  property, you can set the <code>dataTipField</code> property to
     *  use a different property in the data object. An example would be 
	 *  "FullName" when viewing a
     *  set of people's names retrieved from a database.
     * 
     *  @default null
     */
    public function get dataTipField():String
    {
        return _dataTipField;
    }

    /**
     *  @private
     */
    public function set dataTipField(value:String):void
    {
        _dataTipField = value;

        itemsSizeChanged = true;
        invalidateDisplayList();

        dispatchEvent(new Event("dataTipFieldChanged"));
    }

    //----------------------------------
    //  dataTipFunction
    //----------------------------------

    /**
     *  @private
     *  Storage for the dataTipFunction property.
     */
    private var _dataTipFunction:Function;

    [Bindable("dataTipFunctionChanged")]
    [Inspectable(category="Data")]

    /**
     *  User-supplied function to run on each item to determine its dataTip.  
	 *  By default, the list looks for a property named <code>label</code> 
	 *  on each data provider item and displays it.
     *  However, some items do not have a <code>label</code> property 
	 *  nor do they have another property that can be used for displaying 
	 *  in the rows. An example is a data set that has lastName and firstName 
	 *  fields, but you want to display full names. You can supply a 
	 *  <code>dataTipFunction</code> that finds the appropriate
     *  fields and return a displayable string. The 
	 *  <code>dataTipFunction</code> is also good for handling formatting
	 *  and localization.
	 *
	 *  <p>The dataTipFunction takes a single argument which is the item
	 *  in the data provider and returns a String:</p>
	 * 
	 *  <blockquote>
	 *  <code>myDataTipFunction(item:Object):String</code>
	 *  </blockquote>
     * 
     *  @default null
     */
    public function get dataTipFunction():Function
    {
        return _dataTipFunction;
    }

    /**
     *  @private
     */
    public function set dataTipFunction(value:Function):void
    {
        _dataTipFunction = value;

        itemsSizeChanged = true;
        invalidateDisplayList();

        dispatchEvent(new Event("dataTipFunctionChanged"));
    }

	//----------------------------------
    //  defaultColumnCount
    //----------------------------------

	/**
	 *  The default number of columns to display.  This value
	 *  is used if the calculation for the number of
	 *  columns results in a value less than 1 when
	 *  trying to calculate the columnCount based on size or
	 *  content.
	 *
	 *  @default 4
	 */
	protected var defaultColumnCount:int = 4;

	//----------------------------------
    //  defaultRowCount
    //----------------------------------

	/**
	 *  The default number of rows to display.  This value
	 *  is used  if the calculation for the number of
	 *  columns results in a value less than 1 when
	 *  trying to calculate the rowCount based on size or
	 *  content.
	 *
	 *  @default 4
	 */
	protected var defaultRowCount:int = 4;

	//----------------------------------
    //  dragEnabled
    //----------------------------------

    /**
     *  @private
	 *  Storage for the dragEnabled property.
     */
    private var _dragEnabled:Boolean = false;

    /**
	 *  A flag that indicates whether you can drag items out of
	 *  this control and drop them on other controls.
     *  If <code>true</code>, dragging is enabled for the control.
     *  If the <code>dropEnabled</code> property is also <code>true</code>,
     *  you can drag items and drop them within this control
	 *  to reorder the items.
     *
     *  @default false
     */
    public function get dragEnabled():Boolean
    {
        return _dragEnabled;
    }

    /**
	 *  @private
	 */
    public function set dragEnabled(value:Boolean):void
    {
        if (_dragEnabled && !value)
        {
            removeEventListener(DragEvent.DRAG_START, dragStartHandler, false);
            removeEventListener(DragEvent.DRAG_COMPLETE,
								dragCompleteHandler, false);
        }

        _dragEnabled = value;

        if (value)
        {
            addEventListener(DragEvent.DRAG_START, dragStartHandler, false,
							 EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler,
							 false, EventPriority.DEFAULT_HANDLER);
        }
    }

    //----------------------------------
    //  dragImage
    //----------------------------------

    /**
     *  Gets an instance of a class that displays the visuals
	 *  during a drag and drop operation.
	 *
	 *  @default mx.controls.listClasses.ListItemDragProxy
     */
    protected function get dragImage():IUIComponent
    {
        var image:ListItemDragProxy = new ListItemDragProxy();
        image.owner = this;
        return image;
    }

    //----------------------------------
    //  dragImageOffsets
    //----------------------------------

    /**
     *  Gets the offset of the drag image for drag and drop.
     */
    protected function get dragImageOffsets():Point
    {
        var pt:Point = new Point;

		var n:int = listItems.length;
        for (var i:int = lockedRowCount; i < n; i++)
        {
            if (selectedData[rowInfo[i].uid])
            {
                pt.x = listItems[i][0].x;
                pt.y = listItems[i][0].y;
            }
        }

        return pt;
    }

    //----------------------------------
    //  dragMoveEnabled
    //----------------------------------

    /**
     *  @private
	 *  Storage for the dragMoveEnabled property.
     */
    private var _dragMoveEnabled:Boolean = false;

    [Inspectable(defaultValue="false")]

    /**
	 *  A flag that indicates whether items can be moved instead
	 *  of just copied from the control as part of a drag-and-drop
	 *  operation.
     *  If <code>true</code>, and the <code>dragEnabled</code> property
     *  is <code>true</code>, items can be moved.
     *  Often the data provider cannot or should not have items removed
	 *  from it, so a MOVE operation should not be allowed during
	 *  drag-and-drop.
	 *
     *  @default false
     */
    public function get dragMoveEnabled():Boolean
    {
    	return _dragMoveEnabled;
    }

    /**
	 *  @private
	 */
	public function set dragMoveEnabled(value:Boolean):void
    {
    	_dragMoveEnabled = value;
	}

    //----------------------------------
    //  dropEnabled
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the <code>dropEnabled</code> property.
	 */
    private var _dropEnabled:Boolean = false;

    [Inspectable(defaultValue="false")]

    /**
	 *  A flag that indicates whether dragged items can be dropped onto the 
	 *  control.
     *
	 *  <p>If you set this property to <code>true</code>,
	 *  the control accepts all data formats, and assumes that
	 *  the dragged data matches the format of the data in the data provider.
	 *  If you want to explicitly check the data format of the data
	 *  being dragged, you must handle one or more of the drag events,
	 *  such as <code>dragOver</code>, and call the DragEvent's
	 *  <code>preventDefault()</code> method to customize
	 *  the way the list class accepts dropped data.</p>
	 *
	 *  @default false
     */
    public function get dropEnabled():Boolean
    {
        return _dropEnabled;
    }

    /**
	 *  @private
	 */
    public function set dropEnabled(value:Boolean):void
    {
        if (_dropEnabled && !value)
        {
            removeEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false);
            removeEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false);
            removeEventListener(DragEvent.DRAG_OVER, dragOverHandler, false);
            removeEventListener(DragEvent.DRAG_DROP, dragDropHandler, false);
        }

        _dropEnabled = value;

        if (value)
        {
            addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false,
							 EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false,
							 EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_OVER, dragOverHandler, false,
							 EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_DROP, dragDropHandler, false,
							 EventPriority.DEFAULT_HANDLER);
        }
    }

	//----------------------------------
    //  explicitColumnCount
    //----------------------------------

	/**
	 *  The column count requested by explicitly setting the
	 *  <code>columnCount</code> property.
	 */
    protected var explicitColumnCount:int = -1;

	//----------------------------------
    //  explicitColumnWidth
    //----------------------------------

	/**
	 *  The column width requested by explicitly setting the 
	 *  <code>columnWidth</code>.
	 */
    protected var explicitColumnWidth:Number;

	//----------------------------------
    //  explicitRowCount
    //----------------------------------

	/**
	 *  The row count requested by explicitly setting
	 *  <code>rowCount</code>.
	 */
    protected var explicitRowCount:int = -1;

	//----------------------------------
    //  explicitRowHeight
    //----------------------------------

	/**
	 *  The row height requested by explicitly setting
	 *  <code>rowHeight</code>.
	 */
    protected var explicitRowHeight:Number;

    //----------------------------------
    //  iconField
    //----------------------------------

    /**
     *  @private
     *  Storage for iconField property.
     */
    private var _iconField:String = "icon";

    [Bindable("iconFieldChanged")]
    [Inspectable(category="Data", defaultValue="")]

    /**
     *  The name of the field in the data provider object that determines what to 
	 *  display as the icon. By default, the list class does not try to display 
	 *  icons with the text in the rows.  However, by specifying an icon 
	 *  field, you can specify a graphic that is created and displayed as an 
	 *  icon in the row.  This property is ignored by DataGrid.
	 *
	 *  <p>The renderers will look in the data provider object for a property of 
	 *  the name supplied as the iconField.  If the value of the property is a 
	 *  Class, it will instantiate that class and expect it to be an instance 
	 *  of an IFlexDisplayObject.  If the value of the property is a String, 
	 *  it will look to see if a Class exists with that name in the application, 
	 *  and if it can't find one, it will also look for a property on the 
	 *  document with that name and expect that property to map to a Class.</p>
	 *
	 *  @default null
     */
    public function get iconField():String
    {
        return _iconField;
    }

    /**
     *  @private
     */
    public function set iconField(value:String):void
    {
        _iconField = value;

        itemsSizeChanged = true;
        invalidateDisplayList();

        dispatchEvent(new Event("iconFieldChanged"));
    }

    //----------------------------------
    //  iconFunction
    //----------------------------------

    /**
     *  @private
     *  Storage for iconFunction property.
     */
    private var _iconFunction:Function;

    [Bindable("iconFunctionChanged")]
    [Inspectable(category="Data")]

    /**
     *  A user-supplied function to run on each item to determine its icon.  
	 *  By default the list does not try to display icons with the text 
	 *  in the rows.  However, by specifying an icon function, you can specify 
	 *  a Class for a graphic that will be created and displayed as an icon 
	 *  in the row.  This property is ignored by DataGrid.
	 *
	 *  <p>The iconFunction takes a single argument which is the item
	 *  in the data provider and returns a Class.</p>
	 * 
	 *  <blockquote>
	 *  <code>iconFunction(item:Object):Class</code>
	 *  </blockquote>
	 * 
     *  @default null
     */
    public function get iconFunction():Function
    {
        return _iconFunction;
    }

    /**
     *  @private
     */
    public function set iconFunction(value:Function):void
    {
        _iconFunction = value;

        itemsSizeChanged = true;
        invalidateDisplayList();

        dispatchEvent(new Event("iconFunctionChanged"));
    }

	//----------------------------------
    //  itemRenderer
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the itemRenderer property.
	 */
    private var _itemRenderer:IFactory;

    [Inspectable(category="Data")]

    /**
     *  The custom item renderer for the control.
     *  You can specify a drop-in, inline, or custom item renderer.
     *
	 *  <p>The default item renderer depends on the component class. 
	 *  The TileList and HorizontalList class use 
	 *  TileListItemRenderer, The List class uses ListItemRenderer.
	 *  The DataGrid class uses DataGridItemRenderer from DataGridColumn.</p>
     */
    public function get itemRenderer():IFactory
    {
        return _itemRenderer;
    }

    /**
	 *  @private
	 */
    public function set itemRenderer(value:IFactory):void
    {
        _itemRenderer = value;

        invalidateSize();
        invalidateDisplayList();

		itemsSizeChanged = true;
		rendererChanged = true;

        dispatchEvent(new Event("itemRendererChanged"));
    }

    //----------------------------------
    //  labelField
    //----------------------------------

    /**
     *  @private
     *  Storage for labelField property.
     */
    private var _labelField:String = "label";

    [Bindable("labelFieldChanged")]
    [Inspectable(category="Data", defaultValue="label")]

    /**
     *  The name of the field in the data provider items to display as the label. 
	 *  By default the list looks for a property named <code>label</code> 
	 *  on each item and displays it.
     *  However, if the data objects do not contain a <code>label</code> 
	 *  property, you can set the <code>labelField</code> property to
     *  use a different property in the data object. An example would be 
	 *  "FullName" when viewing a set of people names fetched from a database.
	 *
	 *  @default "label"
     */
    public function get labelField():String
    {
        return _labelField;
    }

    /**
     *  @private
     */
    public function set labelField(value:String):void
    {
        _labelField = value;

        itemsSizeChanged = true;
        invalidateDisplayList();

        dispatchEvent(new Event("labelFieldChanged"));
    }

    //----------------------------------
    //  labelFunction
    //----------------------------------

    /**
     *  @private
     *  Storage for labelFunction property.
     */
    private var _labelFunction:Function;

    [Bindable("labelFunctionChanged")]
    [Inspectable(category="Data")]

    /**
     *  A user-supplied function to run on each item to determine its label.  
	 *  By default, the list looks for a property named <code>label</code> 
	 *  on each data provider item and displays it.
     *  However, some data sets do not have a <code>label</code> property
     *  nor do they have another property that can be used for displaying.
	 *  An example is a data set that has lastName and firstName fields
     *  but you want to display full names.
     *
     *  <p>You can supply a <code>labelFunction</code> that finds the 
	 *  appropriate fields and returns a displayable string. The 
	 *  <code>labelFunction</code> is also good for handling formatting and 
	 *  localization. </p>
     *
	 *  <p>For most components, the label function takes a single argument
	 *  which is the item in the data provider and returns a String.
	 *  <blockquote>
	 *  <code>labelFunction(item:Object):String</code>
	 *  </blockquote></p>
	 *
	 *  <p>The method signature for the DataGrid and DataGridColumn classes is:
	 *  <blockquote>
     *  <code>labelFunction(item:Object, column:DataGridColumn):String</code><br/>
	 *  </blockquote>
     *  where <code>item</code> contains the DataGrid item object, and
	 *  <code>column</code> specifies the DataGrid column.</p>
	 *
	 *  @default null
     */
    public function get labelFunction():Function
    {
        return _labelFunction;
    }

    /**
     *  @private
     */
    public function set labelFunction(value:Function):void
    {
        _labelFunction = value;

        itemsSizeChanged = true;
        invalidateDisplayList();

        dispatchEvent(new Event("labelFunctionChanged"));
    }

    //----------------------------------
    //  listData
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the listData property.
	 */
	private var _listData:BaseListData;

	[Bindable("dataChange")]
    [Inspectable(environment="none")]

	/**
	 *  
     *  When a component is used as a drop-in item renderer or drop-in
	 *  item editor, Flex initializes the <code>listData</code> property
	 *  of the component with the additional data from the list control.
	 *  The component can then use the <code>listData</code> property
	 *  and the <code>data</code> property to display the appropriate
	 *  information as a drop-in item renderer or drop-in item editor.
     *
     *  <p>You do not set this property in MXML or ActionScript;
	 *  Flex sets it when the component is used as a drop-in item renderer
	 *  or drop-in item editor.</p>
     *
     *  @see mx.controls.listClasses.IDropInListItemRenderer
	 */
    public function get listData():BaseListData
    {
        return _listData;
    }

    /**
	 *  @private
	 */
    public function set listData(value:BaseListData):void
    {
        _listData = value;
    }
    
	//----------------------------------
    //  lockedColumnCount
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the lockedColumnCount property.
	 */
    mx_internal var _lockedColumnCount:int = 0;

    [Inspectable(defaultValue="0")]

    /**
     *  The index of the first column in the control that scrolls.
     *  Columns with indexes that are lower than this value remain fixed
     *  in view.  Not supported by all list classes.
	 * 
     *  @default 0
     */
    public function get lockedColumnCount():int
    {
        return _lockedColumnCount;
    }

    /**
	 *  @private
	 */
    public function set lockedColumnCount(value:int):void
    {
        _lockedColumnCount = value;

        invalidateDisplayList();
    }

	//----------------------------------
    //  lockedRowCount
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the lockedRowCount property.
	 */
    mx_internal var _lockedRowCount:int = 0;

    [Inspectable(defaultValue="0")]

    /**
     *  The index of the first row in the control that scrolls.
     *  Rows above this one remain fixed in view.
	 * 
     *  @default 0
     */
    public function get lockedRowCount():int
    {
        return _lockedRowCount;
    }

    /**
	 *  @private
	 */
    public function set lockedRowCount(value:int):void
    {
        _lockedRowCount = value;

        invalidateDisplayList();
    }

	//----------------------------------
    //  menuSelectionMode
    //----------------------------------

    /**
	 *  A flag that indicates whether menu-style selection
	 *  should be used.
	 *  In a Menu, dragging from
	 *  one renderer into another selects the new one
	 *  and un-selects the old.
     */
    public var menuSelectionMode:Boolean = false;

	//----------------------------------
    //  rowCount
    //----------------------------------

    /**
     *  @private
	 *  Storage for the rowCount property.
     */
    private var _rowCount:int = -1;

    /**
     *  @private
     */
    private var rowCountChanged:Boolean = true;

    /**
     *  Number of rows to be displayed.
     *  If the height of the component has been explicitly set,
     *  this property might not have any effect.
	 * 
     *  @default 4
     */
    public function get rowCount():int
    {
        return _rowCount;
    }

    /**
     *  @private
     */
    public function set rowCount(value:int):void
    {
        explicitRowCount = value;

        if (_rowCount != value)
        {

            setRowCount(value);
            rowCountChanged = true;
            invalidateProperties();

            invalidateSize();
            itemsSizeChanged = true;
            invalidateDisplayList();

            dispatchEvent(new Event("rowCountChanged"));
        }
    }

    /**
     *  Sets the rowCount property without causing
	 *  invalidation or setting the <code>explicitRowCount</code>
	 *  property, which permanently locks in the number of rows.
     */
    protected function setRowCount(v:int):void
    {
        _rowCount = v;
    }

	//----------------------------------
    //  rowHeight
    //----------------------------------

    /**
     *  @private
	 *  Storage for the rowHeight property.
     */
    private var _rowHeight:Number;
    
    /**
     *  @private
     */
	private var rowHeightChanged:Boolean = false;

	[Inspectable(category="General")]

	/**
     *  The height of the rows in pixels.
     *  Unless the <code>variableRowHeight</code> property is
	 *  <code>true</code>, all rows are the same height.  
     *  If not specified, the row height is based on
     *  the font size and other properties of the renderer.
     */
    public function get rowHeight():Number
    {
        return _rowHeight;
    }

    /**
     *  @private
     */
    public function set rowHeight(value:Number):void
    {
        explicitRowHeight = value;

        if (_rowHeight != value)
        {
            setRowHeight(value);

            invalidateSize();
            itemsSizeChanged = true;
            invalidateDisplayList();

            dispatchEvent(new Event("rowHeightChanged"));
        }
    }

    /**
     *  Sets rowHeight without causing invalidation or 
	 *  setting of <code>explicitRowHeight</code> which
	 *  permanently locks in the height of the rows
     */
    protected function setRowHeight(v:Number):void
    {
        _rowHeight = v;
    }

 	//----------------------------------
    //  selectable
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the selectable property.
	 */
    private var _selectable:Boolean = true;

    [Inspectable(defaultValue="true")]

    /**
	 *  A flag that indicates whether the list shows selected items
	 *  as selected.
     *  If <code>true</code>, the control supports selection.
     *  The Menu class, which subclasses ListBase, sets this property to
	 *  <code>false</code> by default, because it doesn't show the chosen
	 *  menu item as selected.
	 *
	 *  @default true
     */
    public function get selectable():Boolean
    {
        return _selectable;
    }

    /**
	 *  @private
	 */
    public function set selectable(value:Boolean):void
    {
        _selectable = value;
    }

	//----------------------------------
    //  selectedIndex
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the selectedIndex property.
	 */
	mx_internal var _selectedIndex:int = -1;

    [Bindable("change")]
    [Bindable("valueCommit")]
    [Inspectable(category="General", defaultValue="-1")]

    /**
     *  The index in the data provider of the selected item.
	 * 
     *  <p>The default value is -1 (no selected item).</p>
     *
     */
    public function get selectedIndex():int
    {
        return _selectedIndex;
    }

    /**
	 *  @private
	 */
    public function set selectedIndex(value:int):void
    {
		if (!collection || collection.length == 0)
		{
			_selectedIndex = value;
			bSelectionChanged = true;
			bSelectedIndexChanged = true;
			invalidateDisplayList();
			return;
		}
		commitSelectedIndex(value);
	}

	//----------------------------------
    //  selectedIndices
    //----------------------------------

    private var _selectedIndices:Array;

    [Bindable("change")]
    [Bindable("valueCommit")]
    [Inspectable(category="General")]

    /**
     *  An array of indices in the data provider of the selected items.  The
     *  items are in the order that the user selected the items.
	 * 
     *  @default [ ]
     */
    public function get selectedIndices():Array
    {
		if (bSelectedIndicesChanged)
			return _selectedIndices;

        return copySelectedItems(false);
    }

    /**
	 *  @private
	 */
	public function set selectedIndices(indices:Array):void
    {
        // trace("queueing indices");
		if (!collection || collection.length == 0)
		{
			_selectedIndices = indices;
			bSelectedIndicesChanged = true;
			bSelectionChanged = true;

			invalidateDisplayList();
			return;
		}

		commitSelectedIndices(indices);
	}

	//----------------------------------
    //  selectedItem
    //----------------------------------

    /**
	 *  @private
	 *  Storage for the selectedItem property.
	 */
	private var _selectedItem:Object;

    [Bindable("change")]
    [Bindable("valueCommit")]
    [Inspectable(environment="none")]

    /**
     *  A reference to the selected item in the data provider.
	 * 
     *  @default null
     */
    public function get selectedItem():Object
    {
        return _selectedItem;
    }

    /**
	 *  @private
	 */
	public function set selectedItem(data:Object):void
    {
		if (!collection || collection.length == 0)
		{
			_selectedItem = data;
			bSelectedItemChanged = true;
			bSelectionChanged = true;

			invalidateDisplayList();
			return;
		}

		commitSelectedItem(data);
    }

	//----------------------------------
    //  selectedItems
    //----------------------------------

    private var _selectedItems:Array;

    [Bindable("change")]
    [Bindable("valueCommit")]
    [Inspectable(environment="none")]

    /**
     *  An array of references to the selected items in the data provider.  The
     *  items are in the order that the user selected the items.
     *  @default [ ]
     */
    public function get selectedItems():Array
    {
		return bSelectedItemsChanged ? _selectedItems : copySelectedItems();
    }

    /**
	 *  @private
	 */
	public function set selectedItems(items:Array):void
    {
		if (!collection || collection.length == 0)
		{
			_selectedItems = items;
			bSelectedItemsChanged = true;
			bSelectionChanged = true;

			invalidateDisplayList();
			return;
		}

		commitSelectedItems(items);
	}

    //----------------------------------
    //  showDataTips
    //----------------------------------

    /**
     *  @private
     *  Storage for the showDataTips property.
     */
    private var _showDataTips:Boolean = false;

    [Bindable("showDataTipsChanged")]
    [Inspectable(category="Data", defaultValue="false")]

    /**
	 *  A flag that indicates whether dataTips are displayed for text in the rows.
     *  If <code>true</code>, dataTips are displayed.  DataTips
	 *  are tooltips designed to show the text that is too long for the row.
	 *  If you set a dataTipFunction, dataTips are shown regardless of whether the
	 *  text is too long for the row.
	 * 
     *  @default false
     */
    public function get showDataTips():Boolean
    {
        return _showDataTips;
    }

    /**
     *  @private
     */
    public function set showDataTips(value:Boolean):void
    {
        _showDataTips = value;

        itemsSizeChanged = true;
        invalidateDisplayList();

        dispatchEvent(new Event("showDataTipsChanged"));
    }

    //----------------------------------
    //  value
    //----------------------------------

    [Bindable("change")]
    [Bindable("valueCommit")]

    /**
     *  The selected item, or the data or label field of the selected item.
	 *  If the selected item is a Number or String
	 *  the value is the item.  If the item is an object, the value is
	 *  the data property if it exists, or the label property if it exists.
     *
     *  <p>Note: Using <code>selectedItem</code> is often preferable. This
	 *  property exists for backward compatibility with older applications</p>
     */
    public function get value():Object
    {
        var item:Object = selectedItem;

        if (!item)
            return null;

        if (typeof(item) != "object")
            return item;

        return item.data != null ? item.data : item.label;
    }

    //----------------------------------
    //  variableRowHeight
    //----------------------------------

    /**
     *  @private
     *  Storage for the variableRowHeight property.
     */
    private var _variableRowHeight:Boolean = false;

    [Inspectable(category="General")]

    /**
	 *  A flag that indicates whether the individual rows can have different
	 *  height.  This property is ignored by TileList and HorizontalList.
     *  If <code>true</code>, individual rows can have different height values.
	 * 
	 *  @default false
     */
    public function get variableRowHeight():Boolean
    {
        return _variableRowHeight;
    }

    /**
     *  @private
     */
    public function set variableRowHeight(value:Boolean):void
    {
        _variableRowHeight = value;
        itemsSizeChanged = true;

        invalidateDisplayList();

        dispatchEvent(new Event("variableRowHeightChanged"));
    }

    //----------------------------------
    //  wordWrap
    //----------------------------------

    /**
     *  @private
     *  Storage for the wordWrap property.
     */
    private var _wordWrap:Boolean = false;

    [Inspectable(category="General")]

    /**
	 *  A flag that indicates whether text in the row should be word wrapped.
     *  If <code>true</code>, enables word wrapping for text in the rows.
	 *  Only takes effect if <code>variableRowHeight</code> is also 
	 *  <code>true</code>
	 *
	 *  @default false
	 */
    public function get wordWrap():Boolean
    {
        return _wordWrap;
    }

    /**
     *  @private
     */
    public function set wordWrap(value:Boolean):void
    {
		if (value == _wordWrap)
			return;

        _wordWrap = value;

        wordWrapChanged = true;
        itemsSizeChanged = true;

        invalidateDisplayList();

        dispatchEvent(new Event("wordWrapChanged"));
    }

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function initializeAccessibility():void
    {
		if (ListBase.createAccessibilityImplementation != null)
            ListBase.createAccessibilityImplementation(this);
    }

    /**
     *  Create objects that are children of this ListBase, in this case
     *  the <code>listContent</code> object that will hold all the item 
	 *  renderers.
     *  Note that the item renderers are not created immediately, but later
     *  when Flex calls the <code>updateDisplayList()</code> method.
     */
    override protected function createChildren():void
    {
        super.createChildren();

        if (!listContent)
        {
            listContent = new ListBaseContentHolder(this);
            listContent.styleName = this;
            addChild(listContent);
        }

        // This invisible layer, which is a child of listContent
        // catches mouse events for all items
        // and is where we put selection highlighting by default.
        if (!selectionLayer)
        {
            selectionLayer = new FlexSprite();
			selectionLayer.name = "selectionLayer";
			selectionLayer.mouseEnabled = false;
            listContent.addChild(selectionLayer);

			// trace("selectionLayer parent set to " + selectionLayer.parent);

			var g:Graphics = selectionLayer.graphics;
            g.beginFill(0, 0); // 0 alpha means transparent
            g.drawRect(0, 0, 10, 10);
            g.endFill();
        }
    }

    /**
     *  Calculates the column width and row height and number of rows and
	 *  columns based on whether properties like <code>columnCount</code>
	 *  <code>columnWidth</code>, <code>rowHeight</code> and 
	 *  <code>rowCount</code> were explicitly set.
	 *
     *  @see mx.core.ScrollControlBase
     */
    override protected function commitProperties():void
    {
		super.commitProperties();

        if (cachedPaddingTopInvalid)
        {
            cachedPaddingTopInvalid = false;
            cachedPaddingTop = getStyle("paddingTop");
            itemsSizeChanged = true;
            invalidateDisplayList();
        }

        if (cachedPaddingBottomInvalid)
        {
            cachedPaddingBottomInvalid = false;
            cachedPaddingBottom = getStyle("paddingBottom");
            itemsSizeChanged = true;
            invalidateDisplayList();
        }

        if (cachedVerticalAlignInvalid)
        {
            cachedVerticalAlignInvalid = false;
            cachedVerticalAlign = getStyle("verticalAlign");
            itemsSizeChanged = true;
            invalidateDisplayList();
        }

        if (columnCountChanged)
        {
			if (_columnCount < 1)
				_columnCount = defaultColumnCount;
            if (!isNaN(explicitWidth) && isNaN(explicitColumnWidth) && explicitColumnCount > 0)
                setColumnWidth((explicitWidth - viewMetrics.left - viewMetrics.right) / columnCount);
            columnCountChanged = false;
        }

        if (rowCountChanged)
        {
			if (_rowCount < 1)
				_rowCount = defaultRowCount;
            if (!isNaN(explicitHeight) && isNaN(explicitRowHeight) && explicitRowCount > 0)
                setRowHeight((explicitHeight - viewMetrics.top - viewMetrics.bottom) / rowCount);
            rowCountChanged = false;
        }
    }

    /**
     *  Calculates the measured width and height of the component based 
	 *  on the <code>rowCount</code>,
     *  <code>columnCount</code>, <code>rowHeight</code> and
     *  <code>columnWidth</code> properties.
	 *
     *  @see mx.core.ScrollControlBase
     */
    override protected function measure():void
    {
		super.measure();

        var o:EdgeMetrics = viewMetrics;
		
		var cc:int = explicitColumnCount < 1 ?
					 defaultColumnCount :
					 explicitColumnCount;
		
		var rc:int = explicitRowCount < 1 ?
					 defaultRowCount :
					 explicitRowCount;

        if (!isNaN(explicitRowHeight))
		{
            measuredHeight = explicitRowHeight * rc + o.top + o.bottom;
			measuredMinHeight = explicitRowHeight * Math.min(rc, 2) +
								o.top + o.bottom;
		}
        else
		{
		    measuredHeight = rowHeight * rc + o.top + o.bottom;
			measuredMinHeight = rowHeight * Math.min(rc, 2) +
								o.top + o.bottom;
		}

        if (!isNaN(explicitColumnWidth))
		{
            measuredWidth = explicitColumnWidth * cc + o.left + o.right;
			measuredMinWidth = explicitColumnWidth * Math.min(cc, 1) +
							   o.left + o.right;
		}
        else
		{
            measuredWidth = columnWidth * cc + o.left + o.right;
			measuredMinWidth = columnWidth * Math.min(cc, 1) +
							   o.left + o.right;
		}

		// Factor out scrollbars if policy == AUTO. See Container.viewMetrics.
		if (verticalScrollPolicy == ScrollPolicy.AUTO &&
			verticalScrollBar)
		{
			measuredWidth -= verticalScrollBar.minWidth;
			measuredMinWidth -= verticalScrollBar.minWidth;
		}
		if (horizontalScrollPolicy == ScrollPolicy.AUTO &&
			horizontalScrollBar)
		{
			measuredHeight -= horizontalScrollBar.minHeight;
			measuredMinHeight -= horizontalScrollBar.minHeight;
		}
    }

    /**
     *  Adds or removes item renderers if the number of displayable items 
	 *  changed.
	 *  Refreshes the item renderers if they might have changed.
	 *  Applies the selection if it was changed programmatically.
	 *
     *  @see mx.core.ScrollControlBase
     */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
    {
        if (oldUnscaledWidth == unscaledWidth &&
        	oldUnscaledHeight == unscaledHeight &&
            !itemsSizeChanged && !bSelectionChanged &&
            !scrollAreaChanged)
		{
            return;
		}
		
		if (oldUnscaledWidth != unscaledWidth)
			itemsSizeChanged = true;

		super.updateDisplayList(unscaledWidth, unscaledHeight);

        var cursorPos:CursorBookmark;

        listContent.move(viewMetrics.left, viewMetrics.top);

   		if (horizontalScrollPolicy == ScrollPolicy.ON &&
			listType == "vertical")
   		{
   			if (isNaN(_maxHorizontalScrollPosition))
   			{
   				listContent.setActualSize(
					(unscaledWidth - viewMetrics.left - viewMetrics.right) * 2,
   					unscaledHeight - viewMetrics.top - viewMetrics.bottom);
   			}
   			else
			{
   				listContent.setActualSize(
   					unscaledWidth - viewMetrics.left - viewMetrics.right +
					_maxHorizontalScrollPosition,
   					unscaledHeight - viewMetrics.top - viewMetrics.bottom);
			}
   		}
   		else
   		{
   			listContent.setActualSize(
   				unscaledWidth - viewMetrics.left - viewMetrics.right,
   				unscaledHeight - viewMetrics.top - viewMetrics.bottom);
   		}

        var collectionHasItems:Boolean = (collection && collection.length > 0);

		if (collectionHasItems)
		{
	        if (!isNaN(horizontalScrollPositionPending))
	        {
				var hPos:Number = Math.min(horizontalScrollPositionPending,
										   maxHorizontalScrollPosition);
				horizontalScrollPositionPending = NaN;
	            super.horizontalScrollPosition = hPos;
	        }

	        if (!isNaN(verticalScrollPositionPending))
	        {
				var vPos:Number = Math.min(verticalScrollPositionPending,
										   maxVerticalScrollPosition);
				verticalScrollPositionPending = NaN;
	            super.verticalScrollPosition = vPos;
	        }
		}

        // Remove the clip mask that was applied to items in the last row.
        removeClipMask();

        // have to resize selection layer without scaling so refill it
		var g:Graphics = selectionLayer.graphics;
		g.clear();
		if (listContent.width > 0 && listContent.height > 0)
		{
			g.beginFill(0x808080, 0);
			g.drawRect(0, 0, listContent.width, listContent.height);
			g.endFill();
		}

		if (rendererChanged)
		{
			rendererChanged = false;
			while (listItems.length)
			{
				var row:Array = listItems.pop();
				while (row.length)
				{
					listContent.removeChild(row.pop());
				}
			}
			while (freeItemRenderers.length)
			{
				listContent.removeChild(freeItemRenderers.pop());
			}
			rowMap = {};
		}

        // optimize layout if only height is changing
        //
        if (oldUnscaledWidth == unscaledWidth &&
        	!scrollAreaChanged &&
        	!itemsSizeChanged &&
			listItems.length > 0 &&
        	iterator &&
        	columnCount == 1)
        {
            var rowIndex:int = listItems.length - 1;
            if (oldUnscaledHeight > unscaledHeight)
            {
                // shrinking, so just toss extra rows
                while (rowIndex >= 0)
                {
                    if (rowInfo[rowIndex].y >= listContent.height)
                    {
                        var colLen:int = listItems[rowIndex].length;
                        for (var j:int = 0; j < colLen; j++)
                            addToFreeItemRenderers(listItems[rowIndex][j]);
                        var uid:String = rowInfo[rowIndex].uid;

                        delete visibleData[uid];
                        removeIndicators(uid);

                        listItems.pop();    // remove the row
						rowInfo.pop();
                        rowIndex--;
                    }
                    else
                        break;
                }
            }
            else
            {
                if (iterator)
                {
                    cursorPos = iterator.bookmark;
					try
					{
						iterator.seek(CursorBookmark.CURRENT, listItems.length - lockedRowCount);
					}
					catch (e:ItemPendingError)
					{
						lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, listItems.length - lockedRowCount)
						e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
														lastSeekPending));
						// trace("IPE in UpdateDisplayList");
						iteratorValid = false;
						// don't do anything, we'll repaint when the data arrives
					}
                }
                var curY:Number = rowInfo[rowIndex].y + rowInfo[rowIndex].height;
                // fill it in
                makeRowsAndColumns(0, curY, listContent.width, listContent.height, 0, rowIndex + 1);
                if (iterator)
				{
					try
					{
	                    iterator.seek(cursorPos, 0);
					}
					catch (e:ItemPendingError)
					{
						// we don't recover here since we'd only get here if the first seek failed.
					}
				}
            }
        }
        else
        {
            if (iterator)
                cursorPos = iterator.bookmark;
            clearIndicators();
            visibleData = {};
            var pt:Point = makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
            if (iterator)
			{
				try
				{
					iterator.seek(cursorPos, 0);
				}
				catch (e:ItemPendingError)
				{
					// we don't recover here since we'd only get here if the first seek failed.
				}
			}
        }

        oldUnscaledWidth = unscaledWidth;
        oldUnscaledHeight = unscaledHeight;

        configureScrollBars();

        // if needed, add a clip mask to the items in the last row
        addClipMask(true);

        itemsSizeChanged = false;
        wordWrapChanged = false;

		if (bSelectionChanged)
		{
			bSelectionChanged = false;

			// bSelectedIndexChanged can be true if the dp was reset.
			// if selectedItem or cousins are also set, we will resolve
			// selectedIndex in there and therefore don't need to process
			// it

			//in each of these cases allow the "unsetting" of a value regardless
			//of whether the collection has items

			if (bSelectedIndicesChanged
			    && (collectionHasItems || (_selectedIndices == null)))
			{
				bSelectedIndicesChanged = false;
				bSelectedIndexChanged = false;
				commitSelectedIndices(_selectedIndices);
			}

			if (bSelectedItemChanged
			    && (collectionHasItems || (_selectedItem == null)))
			{
				bSelectedItemChanged = false;
				bSelectedIndexChanged = false;
				commitSelectedItem(_selectedItem);
			}

			if (bSelectedItemsChanged
    			&& (collectionHasItems || (_selectedItems == null)))
			{
				bSelectedItemsChanged = false;
				bSelectedIndexChanged = false;
				commitSelectedItems(_selectedItems);
			}

			if (bSelectedIndexChanged
			    && (collectionHasItems || (_selectedIndex == -1)))
	        {
				commitSelectedIndex(_selectedIndex);
				bSelectedIndexChanged = false;
			}

		}

		if (keySelectionPending && iteratorValid)
		{
			keySelectionPending = false;
			finishKeySelection();
		}
    }

    /**
     *  @private
     */
    override public function styleChanged(styleProp:String):void
    {
        if (IS_ITEM_STYLE[styleProp])
        {
            itemsSizeChanged = true;
            invalidateDisplayList();
        }
        else if (styleProp == "paddingTop")
        {
            cachedPaddingTopInvalid = true;
            invalidateProperties();
        }
        else if (styleProp == "paddingBottom")
        {
            cachedPaddingBottomInvalid = true;
            invalidateProperties();
        }
        else if (styleProp == "verticalAlign")
        {
            cachedVerticalAlignInvalid = true;
            invalidateProperties();
        }
        else if (listItems)
        {
            var n:int = listItems.length;
			for (var i:int = 0; i < n; i++)
            {
                var m:int = listItems[i].length;
				for (var j:int = 0; j < m; j++)
                {
					if (listItems[i][j])
						listItems[i][j].styleChanged(styleProp);
                }
            }
        }

        super.styleChanged(styleProp);

		if (invalidateSizeFlag)
		{
			itemsNeedMeasurement = true;
            invalidateProperties();
		}

        if (StyleManager.isSizeInvalidatingStyle(styleProp))
            scrollAreaChanged = true;
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Measuring
	//
	//--------------------------------------------------------------------------

    /**
     *  Measures a set of items from the data provider using
	 *  the current item renderer and returns the
	 *  maximum width found.  This method is used to calculate the
	 *  width of the component.  The various ListBase-derived classes
	 *  have slightly different implementations.  DataGrid measures
	 *  its columns instead of data provider items, and TileList
	 *  just measures the first item and assumes all items are the
	 *  same size.
	 *
     *  <p>This method is not implemented in the ListBase class
     *  and must be implemented in the child class.</p>
	 *
     *  <p>A negative <code>index</code> value can be used to specify
     *  that the width calculation includes any headers.</p>
     *
     *  @param index The data provider item at which to start measuring
	 *  the width.
	 *
     *  @param count The number of items to measure in calculating the width.
	 *
	 *	@return The widest of the measured items.
     */
    public function measureWidthOfItems(index:int = -1, count:int = 0):Number
    {
		return NaN;
    }

    /**
     *  Measures a set of items from the data provider using the
	 *  current item renderer and returns the sum of the heights
	 *  of those items.
	 *
     *  <p>This method is not implemented in the ListBase class
     *  and must be implemented in the child class.</p>
	 *
     *  <p>A negative <code>index</code> value can be used to specify
     *  that the height calculation includes any headers.</p>
     *
     *  @param index The data provider item at which to start calculating
	 *  the height.
	 *
     *  @param count The number of items to use in calculating the height.
	 *
	 *  @return the sum of the height of the measured items.
     */
    public function measureHeightOfItems(index:int = -1, count:int = 0):Number
    {
		return NaN;
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Item fields
	//
	//--------------------------------------------------------------------------

   /**
     *  Returns the string the renderer would display for the given data object
	 *  based on the labelField and labelFunction properties.
     *  If the method cannot convert the parameter to a string, it returns a
     *  single space.
	 *
	 *  @param data Object to be rendered.
	 *
	 *  @return The string to be displayed based on the data.
     */
    public function itemToLabel(data:Object):String
    {
        if (data == null)
        	return " ";

        if (labelFunction != null)
            return labelFunction(data);

		if (data is XML)
        {
            try
            {
                if (data[labelField].length() != 0)
                	data = data[labelField];
                //by popular demand, this is a default XML labelField
				//else if (data.@label.length() != 0)
                //	data = data.@label;
            }
            catch (e:Error)
            {
            }
        }
        else if (data is Object)
        {
            try
            {
                if (data[labelField] != null)
                    data = data[labelField];
            }
            catch(e:Error)
            {
            }
        }

        if (data is String)
            return String(data);

        try
        {
            return data.toString();
        }
        catch(e:Error)
        {
        }

        return " ";
    }

    /**
     *  Returns the dataTip string the renderer would display for the given
	 *  data object based on the dataTipField and dataTipFunction properties.
     *  If the method cannot convert the parameter to a string, it returns a
     *  single space.
     *  <p>For use by developers creating subclasses of ListBase or its children.
     *  Not used by application developers.</p>
	 *
	 *  @param data Object to be rendered.
	 *
	 *  @return String displayable string based on the data.
     */
    public function itemToDataTip(data:Object):String
    {
        if (dataTipFunction != null)
            return dataTipFunction(data);

        if (typeof(data) == "object")
        {
            try
            {
                if (data[dataTipField] != null)
                    data = data[dataTipField];
                else if (data.label != null)
                    data = data.label;
            }
            catch(e:Error)
            {
            }
        }

        if (typeof(data) == "string")
            return String(data);

        try
        {
            return data.toString();
        }
        catch(e:Error)
        {
        }

        return " ";
    }

    /**
     *  Returns the class for an icon, if any, for a data item,  
	 *  based on the iconField and iconFunction properties.
	 *  The field in the item can return a string as long as that
	 *  string represents the name of a class in the application.
	 *  The field in the item can also be a string that is the name
	 *  of a variable in the document that holds the class for
	 *  the icon.
	 *  
	 *  @param data The item from which to extract the icon class
	 *  @return The icon for the item, as a class reference or 
	 *  <code>null</code> if none.
     */
    public function itemToIcon(data:Object):Class
    {
        if (data == null)
            return null;

        if (iconFunction != null)
            return iconFunction(data);

        var iconClass:Class;
        var icon:*;

        if (data is XML)
		{
			try
			{
				if (data[iconField].length() != 0)
				{
             	   icon = String(data[iconField]);
             	   if (icon != null)
             	   {
	             	   iconClass =
							Class(systemManager.getDefinitionByName(icon));
					   if (iconClass)
					       return iconClass;

				   	   return document[icon];
				   }
                }
            }
           	catch(e:Error)
           	{
           	}
		}

        else if (data is Object)
        {
            try
            {
                if (data[iconField] != null)
                {
                    if (data[iconField] is Class)
                        return data[iconField];

                    if (data[iconField] is String)
                    {
                        iconClass = Class(systemManager.getDefinitionByName(
												data[iconField]));
						if (iconClass)
							return iconClass;

						return document[data[iconField]];
                    }
                }
            }
            catch(e:Error)
            {
            }
        }

        return null;
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Renderer management
	//
	//--------------------------------------------------------------------------

    /**
	 *  Make enough rows and columns to fill the area
	 *  described by left, top, right, bottom.
	 *  Renderers are created and inserted into the <code>listItems</code>
	 *  array starting at <code>(firstColumn, firstRow)(</code>
	 *  and moving downwards.
	 *
	 *  <p>If <code>byCount</code> and <code>rowsNeeded</code> are specified,
	 *  then just make that many rows and ignore the <code>bottom</code>
	 *  and <code>right</code> parameters.</p>
	 *
	 *  @param left Horizontal pixel offset of area to fill.
	 *
	 *  @param top Vertical pixel offset of area to fill.
	 *
	 *  @param right Horizontal pixel offset of area to fill
	 *  (from left side of component).
	 *
	 *  @param bottom Vertical pixel offset of area to fill
	 *  (from top of component).
	 *
	 *  @param firstColumn Offset into <code>listItems</code> to store
	 *  the first renderer to be created.
	 *
	 *  @param firstRow Offset into <code>listItems</code> to store
	 *  the first renderer to be created.
	 *
	 *  @param byCount If true, make <code>rowsNeeded</code> number of rows
	 *  and ignore <code>bottom</code> parameter
	 *
	 *  @param rowsNeeded Number of rows to create if <code>byCount</code>
	 *  is true;
	 *
	 *  @return A Point containing the number of rows and columns created.
     */
    protected function makeRowsAndColumns(left:Number, top:Number,
										  right:Number, bottom:Number,
									      firstColumn:int, firstRow:int,
									      byCount:Boolean = false,
										  rowsNeeded:uint = 0):Point
    {
        return new Point(0,0);
    }

	/**
	 *  Computes the offset into the data provider of the item
	 *  at colIndex, rowIndex.
	 *  The 9th row 3rd column in a TileList could be different items
	 *  in the data provider based on the direction the tiles are laid
	 *  out and the number of rows and columns in the TileList.
	 *
	 *  @param rowIndex The 0-based index of the row, including rows
	 *  scrolled off the top.  Thus, if <code>verticalScrollPosition</code>
	 *  is 2 then the first visible row has a rowIndex of 2.
	 *
	 *  @param colIndex The 0-based index of the column, including
	 *  columns scrolled off the left.  If 
	 *  <code>horizontalScrollPosition</code> is 2 then the first column
	 *  on the left has a columnIndex of 2.
	 *
	 *  @return The offset into the data provider.
	 */
	public function indicesToIndex(rowIndex:int, colIndex:int):int
	{
		return rowIndex * columnCount + colIndex;
	}

	/**
	 *  The row for the data provider item at the given index.
	 *
	 *  @param index The offset into the data provider.
	 *
	 *  @return The row the item would be displayed at in the component.
	 */
	protected function indexToRow(index:int):int
	{
		return index;
	}

	/**
	 *  The column for the data provider item at the given index.
	 *
	 *	@param index The offset into the data provider
	 *
	 *  @return The column the item would be displayed at in the component.
	 */
	protected function indexToColumn(index:int):int
	{
		return 0;
	}

    /**
     *  @private
	 *  Used by accessibility.
     */
	mx_internal function indicesToItemRenderer(row:int, 
											   col:int):IListItemRenderer
	{
		 return listItems[row][col];
	}

    /**
	 *  Returns a Point containing the columnIndex and rowIndex of an
	 *  item renderer.  Since item renderers are only created for items
	 *  within the set of viewable rows
	 *  you cannot use this method to get the indices for items
	 *  that are not visible.  Also note that item renderers
	 *  are recycled so the indices you get for an item may change
	 *  if that item renderer is reused to display a different item.
	 *  Usually, this method is called during mouse and keyboard handling
	 *  when the set of data displayed by the item renderers hasn't yet
	 *  changed.
	 *
	 *  @param item An item renderer
	 *
	 *  @return A Point.  The <code>x</code> property is the columnIndex
	 *  and the <code>y</code> property is the rowIndex.
     */
    protected function itemRendererToIndices(item:IListItemRenderer):Point
    {
        if (item.name in rowMap)
        {
            var index:int = rowMap[item.name].rowIndex;
            var len:int = listItems[index].length;
            for (var i:int = 0; i < len; i++)
            {
                if (listItems[index][i] == item)
                    break;
            }

            return new Point(i < lockedColumnCount ?
							 i :
							 i + horizontalScrollPosition,
							 index < lockedRowCount ?
							 index :
							 index + verticalScrollPosition);
        }
        else
		{
            return null;
		}
    }

    /**
     *  Get an item renderer for the index of an item in the data provider,
	 *  if one exists.  Since item renderers only exist for items 
	 *  within the set of viewable rows
	 *  items, you cannot use this method for items that are not visible.
	 *
	 *  @param index The offset into the data provider for an item
	 *
	 *  @return The item renderer that is displaying the item, or 
	 *  <code>null</code> if the item is not currently displayed.
     */
    public function indexToItemRenderer(index:int):IListItemRenderer
    {
        if (index < verticalScrollPosition ||
            index >= verticalScrollPosition + listItems.length)
        {
            return null;
        }

        return listItems[index - verticalScrollPosition][0];
    }

	/**
	 *  Returns the index of the item in the data provider of the item
	 *  being rendered by this item renderer.  Since item renderers
	 *  only exist for items that are within the set of viewable
	 *  rows, you cannot
	 *  use this method for items that are not visible.
	 *
	 *  @param itemRenderer The item renderer that is displaying the
	 *  item for which you want to know the data provider index.
	 *
	 *  @return The index of the item in the data provider
	 */
    public function itemRendererToIndex(itemRenderer:IListItemRenderer):int
    {
        if (itemRenderer.name in rowMap)
        {
            var index:int = rowMap[itemRenderer.name].rowIndex;
            return index < lockedRowCount ?
				   index :
				   index + verticalScrollPosition;
        }
        else
		{
            return int.MIN_VALUE;
		}
    }

    /**
     *  Determines the UID for a data provider item.  All items
	 *  in a data provider must either have a unique ID (UID)
	 *  or one will be generated and associated with it.  This
	 *  means that you cannot have an object or scalar value
	 *  appear twice in a data provider.  For example, the following
	 *  data provider is not supported because the value "foo"
	 *  appears twice and the UID for a string is the string itself
	 *
	 *  <blockquote>
	 *  <code>var sampleDP:Array = ["foo", "bar", "foo"]</code>
	 *  </blockquote>
	 *
	 *	Simple dynamic objects can appear twice if they are two
	 *  separate instances.  The following is supported because
	 *  each of the instances will be given a different UID because
	 *  they are different objects.
	 *
	 *  <blockquote>
	 *  <code>var sampleDP:Array = [{label: "foo"}, {label: "foo"}]</code>
	 *  </blockquote>
	 *
	 *  Note that the following is not supported because the same instance
	 *  appears twice.
	 *
	 *  <blockquote>
	 *  <code>var foo:Object = {label: "foo"};
	 *  sampleDP:Array = [foo, foo];</code>
	 *  </blockquote>
	 *
	 *  @param data The data provider item
	 *
	 *  @return The UID as a string
     */
    protected function itemToUID(data:Object):String
    {
    	if (!data)
			return "null";

        return UIDUtil.getUID(data);
    }

    /**
     *  Returns the item renderer for a given item in the data provider,
	 *  if there is one.  Since item renderers only exist for items
	 *  that are within the set of viewable rows, this method
	 *  returns <code>null</code> if the item is not visible.
	 *  For DataGrid, this will return the first column's renderer.
	 *
	 *  @param item The data provider item
	 *
	 *  @return The item renderer or <code>null</code> if the item is not 
	 *  currently displayed.
     */
    public function itemToItemRenderer(item:Object):IListItemRenderer
    {
        return visibleData[itemToUID(item)];
    }

    /**
     *  Determines if an item is being displayed by a renderer.
	 *
	 *  @param item A data provider item.
	 *  @return <code>true</code> if the item is being displayed.
     */
    public function isItemVisible(item:Object):Boolean
    {
        return itemToItemRenderer(item) != null;
    }

    /**
     *  Determines which item renderer is under the mouse.  Item
	 *  renderers can be made of multiple mouse targets, or have 
	 *  visible areas that are not mouse targets.  This method
	 *  checks both targets and position to determine which
	 *  item renderer the mouse is over from the user's perspective,
	 *  which can differ from the information provied by the 
	 *  mouse event.
	 *  
	 *  @param event A MouseEvent that contains the position of
	 *  the mouse and the object it is over
	 *
	 *  @return The item renderer the mouse is over or 
	 *  <code>null</code> if none.
     */
	protected function mouseEventToItemRenderer(
								event:MouseEvent):IListItemRenderer
	{
		return mouseEventToItemRendererOrEditor(event);
	}

	/**
	 *  @private
	 */
	mx_internal function mouseEventToItemRendererOrEditor(
								event:MouseEvent):IListItemRenderer
	{
		var target:DisplayObject = DisplayObject(event.target);
		if (target == listContent)
		{
			var pt:Point = new Point(event.stageX, event.stageY);
			pt = listContent.globalToLocal(pt);
			
			var yy:Number = 0;
			
			var n:int = listItems.length;
			for (var i:int = 0; i < n; i++)
			{
				if (listItems[i].length)
				{
					if (pt.y < yy + rowInfo[i].height)
					{
						var m:int = listItems[i].length;
						if (m == 1)
							return listItems[i][0];
						
						var j:int = Math.floor(pt.x / columnWidth);
						return listItems[i][j];
					}
				}
				yy += rowInfo[i].height;
			}
		}
		else if (target == highlightIndicator)
		{
			return lastHighlightItemRenderer;
		}

        while (target && target != this)
        {
            if (target is IListItemRenderer && target.parent == listContent)
            {
                if (target.visible)
                    return IListItemRenderer(target);
                break;
            }

            target = target.parent;
        }

		return null;
	}

    /**
     *  @private
     *  Helper function for addClipMask().
	 *  Returns true if all of the IListItemRenderers are UITextFields.
     */
    private function hasOnlyTextRenderers():Boolean
    {
        if (listItems.length == 0)
            return true;

        var rowItems:Array = listItems[listItems.length - 1];
        var n:int = rowItems.length;
        for (var i:int = 0; i < n; i++)
        {
            if (!(rowItems[i] is UITextField))
                return false;
        }

        return true;
    }

	/**
	 *  Determines whether a renderer contains (or owns) a display object.
	 *  Ownership means that the display object isn't actually parented
	 *  by the renderer but is associated with it in some way.  Popups
	 *  should be owned by the renderers so that activity in the popup
	 *  is associated with the renderer and not seen as activity in another
	 *  component.
	 *
	 *  @param renderer The renderer that might contain or own the 
	 *  display object.
	 *
	 *  @param object The display object that might be associated with the
	 *  renderer.
	 *
	 *  @return <code>true</code> if the display object is contained
	 *  or owned by the renderer.
	 */
	public function itemRendererContains(renderer:IListItemRenderer,
										 object:DisplayObject):Boolean
	{
		if (!object)
			return false;

		if (!renderer)
			return false;

		return renderer.owns(object);
	}

    /**
	 *  Adds a renderer to the recycled renderer list,
	 *  making it invisible and cleaning up references to it.
	 *
	 *  @param item IListItemRenderer
     */
    protected function addToFreeItemRenderers(item:IListItemRenderer):void
    {
		// trace("addToFreeItemRenderers ", item);

        DisplayObject(item).visible = false;
        freeItemRenderers.push(item);
        delete rowMap[item.name];
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Drawing
	//
	//--------------------------------------------------------------------------

    /**
	 *  Draws any alternating row colors, borders and backgrounds for the rows.
     */
    protected function drawRowBackgrounds():void
    {
    }

    /**
	 *  Draws the renderer with indicators
	 *  that it is highlighted, selected, or the caret.
	 *
	 *  @param item The renderer.
	 *  @param selected <code>true</code> if the renderer should be drawn in
	 *  its selected state.
	 *  @param highlighted <code>true</code> if the renderer should be drawn in
	 *  its highlighted state.
	 *  @param caret <code>true</code> if the renderer should be drawn as if
	 *  it is the selection caret.
	 *  @param transition <code>true</code> if the selection state should fade in
	 *  via an effect.
     */
    protected function drawItem(item:IListItemRenderer,
								selected:Boolean = false,
								highlighted:Boolean = false,
								caret:Boolean = false,
								transition:Boolean = false):void
    {
        var o:Sprite;
		var g:Graphics;

		if (!item)
			return;

        var rowData:BaseListData = rowMap[item.name];

        // trace("drawitem " + rowData.uid + " " + selected + " " + highlighted + " " + caret);

        if (highlighted &&
			(!highlightItemRenderer || highlightUID != rowData.uid))
        {
            if (!highlightIndicator)
            {
                o = new SpriteAsset();
                selectionLayer.addChild(DisplayObject(o));
                highlightIndicator = o;
            }
            else
			{
                selectionLayer.setChildIndex(DisplayObject(highlightIndicator),
											 selectionLayer.numChildren - 1);
            }

			o = highlightIndicator;

			drawHighlightIndicator(
				o, item.x, rowInfo[rowData.rowIndex].y,
				item.width, rowInfo[rowData.rowIndex].height, 
				getStyle("rollOverColor"), item);

			lastHighlightItemRenderer = highlightItemRenderer = item;
            highlightUID = rowData.uid;
        }
        else if (!highlighted && highlightItemRenderer && (rowData && highlightUID == rowData.uid) )
        {
            if (highlightIndicator)
                Sprite(highlightIndicator).graphics.clear();
            highlightItemRenderer = null;
            highlightUID = "";
        }

        if (selected)
        {
			if (!selectionIndicators[rowData.uid])
			{
				o = new SpriteAsset();
				o.mouseEnabled = false;
				selectionLayer.addChild(DisplayObject(o));
				selectionIndicators[rowData.uid] = o;
				
				drawSelectionIndicator(
					o, item.x, rowInfo[rowData.rowIndex].y,
					item.width, rowInfo[rowData.rowIndex].height,
					enabled ?
					getStyle("selectionColor") :
					getStyle("selectionDisabledColor"),
					item);
				
				if (transition)
					applySelectionEffect(o, rowData.uid, item);
			}
			else
			{
				o = selectionIndicators[rowData.uid];
				
				drawSelectionIndicator(
					o, item.x, rowInfo[rowData.rowIndex].y,
					item.width, rowInfo[rowData.rowIndex].height,
					enabled ?
					getStyle("selectionColor") :
					getStyle("selectionDisabledColor"),
					item);
			}

        }
        else if (!selected)
        {
            if (rowData && selectionIndicators[rowData.uid])
            {
            	if (selectionTweens[rowData.uid])
            	{
                    selectionTweens[rowData.uid].removeEventListener(
						TweenEvent.TWEEN_UPDATE, selectionTween_updateHandler);
                    selectionTweens[rowData.uid].removeEventListener(
						TweenEvent.TWEEN_END, selectionTween_endHandler);
                    if (selectionIndicators[rowData.uid].alpha < 1)
                        Tween.removeTween(selectionTweens[rowData.uid]);
                    delete selectionTweens[rowData.uid];
                }

                selectionLayer.removeChild(selectionIndicators[rowData.uid]);
                delete selectionIndicators[rowData.uid]
            }
        }

        if (caret) // && (!caretItemRenderer || caretUID != rowData.uid))
        {
            // Only draw the caret if there has been keyboard navigation.
            if (showCaret)
            {
                if (!caretIndicator)
                {
                    o = new SpriteAsset();
					o.mouseEnabled = false;
                    selectionLayer.addChild(DisplayObject(o));
                    caretIndicator = o;
                }
                else
				{
                    selectionLayer.setChildIndex(DisplayObject(caretIndicator),
												 selectionLayer.numChildren - 1);
                }
				
				o = caretIndicator;
				
				drawCaretIndicator(
					o, item.x, rowInfo[rowData.rowIndex].y,
					item.width, rowInfo[rowData.rowIndex].height,
					getStyle("selectionColor"), item);

				caretItemRenderer = item;
                caretUID = rowData.uid;
            }
        }
        else if (!caret && caretItemRenderer && caretUID == rowData.uid)
        {
            if (caretIndicator)
                Sprite(caretIndicator).graphics.clear();
            caretItemRenderer = null;
            caretUID = "";
        }

        if (item is IFlexDisplayObject)
        {
			if (item is IInvalidating)
			{
				IInvalidating(item).invalidateDisplayList();
				IInvalidating(item).validateNow();
			}
        }
        else if (item is UITextField)
        {
            UITextField(item).validateNow();
        }
    }

	/**
	 *  Draws the highlight indicator into the given Sprite
	 *  at the position, width and height specified using the
	 *  color specified.
	 * 
	 *  @param indicator A Sprite that should contain the graphics
	 *  for that make a renderer look highlighted
	 *  @param x The suggested x position for the indicator
	 *  @param y The suggested y position for the indicator
	 *  @param width The suggested width for the indicator
	 *  @param height The suggested height for the indicator
	 *  @param color The suggested color for the indicator
	 *  @param itemRenderer The item renderer that is being highlighted
	 */
	protected function drawHighlightIndicator(
								indicator:Sprite, x:Number, y:Number,
								width:Number, height:Number, color:uint,
								itemRenderer:IListItemRenderer):void
	{
		var g:Graphics = Sprite(indicator).graphics;
		g.clear();
		g.beginFill(color);
		g.drawRect(0, 0, width, height);
		g.endFill();
		
		indicator.x = x;
		indicator.y = y;
	}

	/**
	 *  Draws the selection indicator into the given Sprite
	 *  at the position, width and height specified using the
	 *  color specified.
	 * 
	 *  @param indicator A Sprite that should contain the graphics
	 *  for that make a renderer look highlighted
	 *  @param x The suggested x position for the indicator
	 *  @param y The suggested y position for the indicator
	 *  @param width The suggested width for the indicator
	 *  @param height The suggested height for the indicator
	 *  @param color The suggested color for the indicator
	 *  @param itemRenderer The item renderer that is being highlighted
	 */
	protected function drawSelectionIndicator(
								indicator:Sprite, x:Number, y:Number,
								width:Number, height:Number, color:uint,
								itemRenderer:IListItemRenderer):void
	{
		var g:Graphics = Sprite(indicator).graphics;
		g.clear();
		g.beginFill(color);
		g.drawRect(0, 0, width, height);
		g.endFill();
		
		indicator.x = x;
		indicator.y = y;
	}

	/**
	 *  Draws the caret indicator into the given Sprite
	 *  at the position, width and height specified using the
	 *  color specified.
	 * 
	 *  @param indicator A Sprite that should contain the graphics
	 *  for that make a renderer look highlighted
	 *  @param x The suggested x position for the indicator
	 *  @param y The suggested y position for the indicator
	 *  @param width The suggested width for the indicator
	 *  @param height The suggested height for the indicator
	 *  @param color The suggested color for the indicator
	 *  @param itemRenderer The item renderer that is being highlighted
	 */
	protected function drawCaretIndicator(
								indicator:Sprite, x:Number, y:Number,
								width:Number, height:Number, color:uint,
								itemRenderer:IListItemRenderer):void
	{
		var g:Graphics = Sprite(indicator).graphics;
		g.clear();
		g.lineStyle(1, color, 1);
		g.drawRect(0, 0, width - 1, height - 1);
		
		indicator.x = x;
		indicator.y = y;
	}

    /**
     *  Removes all selection and highlight and caret indicators.
     */
    protected function clearIndicators():void
    {
        for (var uniqueID:String in selectionTweens)
        {
        	removeIndicators(uniqueID);
        }

        while (selectionLayer.numChildren > 0)
		{
            selectionLayer.removeChildAt(0);
		}
        
		selectionTweens = {};
        selectionIndicators = {};
        
		highlightIndicator = null;
        highlightUID = null;
        
		caretIndicator = null;
        caretUID = null;
    }

    /**
	 *  Cleans up selection highlights and other associated graphics
	 *  for a given item in the data provider.
	 *
	 *  @param uid The UID of the data provider item
     */
    protected function removeIndicators(uid:String):void
    {
		if (selectionTweens[uid])
		{
			selectionTweens[uid].removeEventListener(
				TweenEvent.TWEEN_UPDATE, selectionTween_updateHandler);
		
			selectionTweens[uid].removeEventListener(
				TweenEvent.TWEEN_END, selectionTween_endHandler);

			if (selectionIndicators[uid].alpha < 1)
				Tween.removeTween(selectionTweens[uid]);
			
			delete selectionTweens[uid];
		}

        // toss associated graphics if needed

		if (selectionIndicators[uid])
        {
            selectionLayer.removeChild(selectionIndicators[uid]);
            selectionIndicators[uid] = null;
        }

        if (uid == highlightUID)
        {
            highlightItemRenderer = null;
            highlightUID = null;
            if (highlightIndicator)
                Sprite(highlightIndicator).graphics.clear();
        }

        if (uid == caretUID)
        {
            caretItemRenderer = null;
            caretUID = null;
            if (caretIndicator)
                Sprite(caretIndicator).graphics.clear();
        }
    }

    /**
     *  @private
	 */
	mx_internal function clearHighlight(item:IListItemRenderer):void
	{
		var uid:String = itemToUID(item.data);
        
		drawItem(visibleData[uid], isItemSelected(item.data),
				 false, uid == caretUID);

		var pt:Point = itemRendererToIndices(item);
        if (pt)
        {
            var listEvent:ListEvent =
                new ListEvent(ListEvent.ITEM_ROLL_OUT);
            listEvent.columnIndex = pt.x;
            listEvent.rowIndex = pt.y;
			listEvent.itemRenderer = item;
            dispatchEvent(listEvent);
        }
    }

    /**
     *  Refresh all rows on next update.
     */
    public function invalidateList():void
    {
		itemsSizeChanged = true;
		invalidateDisplayList();
    }

    /**
     *  Refreshes all rows now.  Calling this method can require substantial
	 *  processing, because can be expensive at it completely redraws all renderers
	 *  in the list and won't return until complete.
     */
    protected function updateList():void
    {
        // trace("updateList " + verticalScrollPosition);
		
		removeClipMask();
        
		var cursorPos:CursorBookmark = (iterator) ? iterator.bookmark : null;
        
		clearIndicators();
        
		visibleData = {}
        
		makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
        
		if (iterator)
            iterator.seek(cursorPos, 0);
        
		drawRowBackgrounds();
        
		configureScrollBars();
		
		addClipMask(true);
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Clipping
	//
	//--------------------------------------------------------------------------

    /**
     *  @private
     *  By default, there's a single large clip mask applied to the entire
     *  listContent area of the List.  When the List contains a mixture of
     *  device text and vector graphics (e.g.: there are custom item renderers),
     *  that clip mask imposes a rendering overhead.
     *
     *  When graphical (non-text) item renderers are used, we optimize by only
     *  applying a clip mask to the list items in the last row ... and then
     *  only when it's needed.
     *
     *  This optimization breaks down when there's a horizontal scrollbar.
     *  Rather than attempting to apply clip masks to every item along the left
     *  and right edges, we give up and use the default clip mask that covers
     *  the entire List.
     *
     *  For Lists and DataGrids containing custom item renderes, this
     *  optimization yields a 25% improvement in scrolling speed.
     */
    mx_internal function addClipMask(layoutChanged:Boolean):void
    {
        // If something about the List has changed, check to see if we need
        // to clip items in the last row.
        if (layoutChanged)
        {
            if (horizontalScrollBar || hasOnlyTextRenderers())
            {
                // As described above, we just use the default clip mask
                // when there's a horizontal scrollbar or the item renders
                // are all UITextFields.
                listContent.mask = maskShape;
                selectionLayer.mask = null;
            }
            else
            {
                // When we're not applying the default clip mask to the whole
                // listContent, we still want to apply it to the selectionLayer
                // (so that the selection rectangle and the mouseOver rectangle
                // are properly clipped)
                listContent.mask = null;
                selectionLayer.mask = maskShape;
            }
        }

        // If we've decided to clip the entire listContent, then stop here.
        // There's no need to clip individual items
        if (listContent.mask)
            return;

        // If the last row fits inside listContent, then stop here.  There's
        // no need to do any clipping.
        var lastRowIndex:int = listItems.length - 1;
        var lastRowInfo:ListRowInfo = rowInfo[lastRowIndex];
        var lastRowItems:Array = listItems[lastRowIndex];
        if (lastRowInfo.y + lastRowInfo.height <= listContent.height)
            return;

        // For each list item in the last row, either apply a clip mask or
        // set the row's height to not exceed the height of listContent
        var numColumns:int = lastRowItems.length;
        var rowY:Number = lastRowInfo.y;
        var rowWidth:Number = listContent.width;
        var rowHeight:Number = listContent.height - lastRowInfo.y;
        for (var i:int = 0; i < numColumns; i++)
        {
            var item:DisplayObject = lastRowItems[i];
			var yOffset:Number = item.y - rowY;
            if (item is UITextField)
                item.height = rowHeight - yOffset;
            else
                item.mask = createItemMask(0, rowY + yOffset, rowWidth, rowHeight - yOffset);
        }
    }

    /**
     *  @private
     *  Helper function for addClipMask().
	 *  Creates a clip mask with the specified dimensions.
     */
    private function createItemMask(x:Number, y:Number,
									width:Number, height:Number):DisplayObject
    {
        var mask:Shape;

        // To avoid constantly creating and destroying clip masks, we'll
        // maintain a "free list" of masks that are not currently being
        // used.  Items are added to the free list in removeClipMask, below.
        if (!itemMaskFreeList)
            itemMaskFreeList = [];

        if (itemMaskFreeList.length > 0)
        {
            mask = itemMaskFreeList.pop();

            if (mask.width != width)
                mask.width = width;
            if (mask.height != height)
                mask.height = height;
        }
        else
        {
            mask = new FlexShape();
			mask.name = "mask";

            var g:Graphics = mask.graphics;
            g.beginFill(0xFFFFFF);
            g.drawRect(0, 0, width, height);
            g.endFill();

            mask.visible = false;
            listContent.addChild(mask);
        }

        if (mask.x != x)
            mask.x = x;
        if (mask.y != y)
            mask.y = y;

        return mask;
    }

    /**
     *  @private
     *
     *  Undo the effects of the addClipMask function (above)
     */
    mx_internal function removeClipMask():void
    {
        // If we're currently using the default clip mask to clip the entire
        // listContent, then there's no need to undo clipping on individual
        // list items.
        if (listContent && listContent.mask)
            return;

        // If there are no rows, do nothing.
        var lastRowIndex:int = listItems.length - 1;
        if (lastRowIndex < 0)
            return;

        // Undo the effects of the last "for" loop in addClipMask
        var rowHeight:Number = rowInfo[lastRowIndex].height;
        var lastRowInfo:ListRowInfo = rowInfo[lastRowIndex];
        var lastRowItems:Array = listItems[lastRowIndex];
        var numColumns:int = lastRowItems.length;
        for (var i:int = 0; i < numColumns; i++)
        {
            var item:DisplayObject = lastRowItems[i];
            if (item is UITextField)
            {
                if (item.height != rowHeight - (item.y - lastRowInfo.y))
                    item.height = rowHeight - (item.y - lastRowInfo.y);
            }
            else if (item && item.mask)
            {
                itemMaskFreeList.push(item.mask);
                item.mask = null;
            }
        }
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Highlighting and selection
	//
	//--------------------------------------------------------------------------

    /**
     *  Determines if the item renderer for a data provider item 
	 *  is highlighted (is rolled over via the mouse or
	 *  or under the caret via keyboard navigation).
	 *
	 *  @param data The data provider item
	 *  @return <code>true</code> if the item is highlighted
     */
    public function isItemHighlighted(data:Object):Boolean
    {
        if (!data)
			return false;

		// When something is selected, the selection indicator
		// overlays the highlighted indicator so we want
		// to draw as selected and not highlighted.
		var isSelected:Boolean = highlightIndicator &&
			(selectionLayer.getChildIndex(highlightIndicator) !=
			selectionLayer.numChildren - 1)

		if (data is String)
			return (data == highlightUID && !isSelected);

        return itemToUID(data) == highlightUID && !isSelected;
    }

    /**
     *  Determines if the item renderer for a data provider item 
	 *  is selected.
	 *
	 *  @param data The data provider item
	 *  @return <code>true</code> if the item is highlighted
     */
    public function isItemSelected(data:Object):Boolean
    {
        if (!data)
			return false;

		if (data is String)
			return (selectedData[data] != undefined)

        return selectedData[itemToUID(data)] != undefined;
    }

    /**
     *  @private
     */
    private function calculateSelectedIndexAndItem():void
    {
        var num:int = 0;
        for (var p:String in selectedData)
        {
            num = 1;
            break;
        }

        if (!num)
        {
            _selectedIndex = -1;
            _selectedItem = null;
            return;
        }

		_selectedIndex = selectedData[p].index;
		_selectedItem = selectedData[p].data;
	}

    /**
     *  Updates the set of selected items given that the item renderer provided
	 *  was clicked by the mouse and the keyboard modifiers are in the given
	 *  state.  This method also updates the display of the item renderers based
	 *  on their updated selected state.
	 *
	 *  @param item The item renderer that was clicked
	 *  @param shiftKey <code>true</code> if the shift key was held down when
	 *  the mouse was clicked.
	 *  @param ctrlKey <code>true</code> if the ctrl key was held down when
	 *  the mouse was clicked.
	 *  @param transition <code>true</code> if the graphics for the selected 
	 *  state should be faded in using an effect.
	 *
     *  @return <code>true</code> if the set of selected items changed.
	 *  Clicking on an already-selected item doesn't always change the set
	 *  of selected items.
     */
    protected function selectItem(item:IListItemRenderer,
								  shiftKey:Boolean, ctrlKey:Boolean,
								  transition:Boolean = true):Boolean
    {
        if (!selectable)
        	return false;

        // Begin multiple selection cases.
        // We'll start by assuming the selection has changed.
        var selectionChange:Boolean = false;
        var placeHolder:CursorBookmark = iterator.bookmark;
        var index:int = itemRendererToIndex(item);
        var data:Object;
        var uid:String = itemToUID(item.data);

        if (!allowMultipleSelection || (!shiftKey && !ctrlKey))
        {
            // Case one:just a plain old click
            if (ctrlKey && selectedData[uid])
            {
            	selectionChange = true;
            	clearSelected(transition);
            }
            else if (_selectedIndex != index || bSelectedIndexChanged)
            {
				selectionChange = true;

	            //Clear all other selections, this is a single click
	            clearSelected(transition);
	            addSelectionData(uid, new ListBaseSelectionData(item.data, index, approximate));
	            drawItem(visibleData[uid], true, uid == highlightUID, true, transition);
	            _selectedIndex = index;
	            _selectedItem = item.data;
	            iterator.seek(CursorBookmark.CURRENT, _selectedIndex - verticalScrollPosition);
	            caretIndex = _selectedIndex;
	            caretBookmark = iterator.bookmark;
	            anchorIndex = _selectedIndex;
	            anchorBookmark = iterator.bookmark;
	            iterator.seek(placeHolder, 0);
	        }
        }
        
		else if (shiftKey && allowMultipleSelection)
        {
            // trace("begin shiftsel");
            if (anchorBookmark)
            {
                var oldAnchorBookmark:CursorBookmark = anchorBookmark;
                var oldAnchorIndex:int = anchorIndex;
                var incr:Boolean = (anchorIndex < index);
                clearSelected(false);
                caretIndex = index;
                caretBookmark = iterator.bookmark;
                anchorIndex = oldAnchorIndex;
                anchorBookmark = oldAnchorBookmark;

				try
				{
	                iterator.seek(anchorBookmark, 0);
				}
				catch (e:ItemPendingError)
				{
					e.addResponder(new ItemResponder(selectionPendingResultHandler, selectionPendingFailureHandler,
														new ListBaseSelectionPending(incr, index, item.data, transition, placeHolder, CursorBookmark.CURRENT, 0)));
					iteratorValid = false;
				}

				shiftSelectionLoop(incr, anchorIndex, item.data, transition, placeHolder);
            }

            // selection may or may not change for this case.
            // but requires complicated testing.
			// so just assume that selection changed.
            selectionChange = true;
            // trace("end shiftsel");
        }
        
		else if (ctrlKey && allowMultipleSelection)
        {
            if (selectedData[uid])
            {
                removeSelectionData(uid);
                drawItem(visibleData[uid], false, uid == highlightUID, true, transition);
                if (item.data == selectedItem)
                    calculateSelectedIndexAndItem();
            }
            else
            {
                addSelectionData(uid, new ListBaseSelectionData(item.data, index, approximate));
                drawItem(visibleData[uid], true, uid == highlightUID, true, transition);
                _selectedIndex = index;
                _selectedItem = item.data;
            }
            iterator.seek(CursorBookmark.CURRENT, index - verticalScrollPosition);
            caretIndex = index;
            caretBookmark = iterator.bookmark;
            anchorIndex = index;
            anchorBookmark = iterator.bookmark;
            iterator.seek(placeHolder, 0);

            // if user is clicking with ctl key then
            // seletion gets changed always.
            selectionChange = true;
        }

        return selectionChange;
    }

    /**
     *  @private
     */
    private function shiftSelectionLoop(incr:Boolean, index:int,
										stopData:Object, transition:Boolean,
										placeHolder:CursorBookmark):void
    {
		var data:Object;
		var uid:String;

		try
		{
			do
			{
				data = iterator.current;
				uid = itemToUID(data);
				// trace(uid);
				addSelectionData(uid, new ListBaseSelectionData(data, index, approximate));
				if (visibleData[uid])
					drawItem(visibleData[uid], true, uid == highlightUID, false, transition);
				if (data == stopData)
				{
					if (visibleData[uid])
						drawItem(visibleData[uid], true, uid == highlightUID, true, transition);
					break;
				}
				if (incr)
					index++;
				else
					index--;

			}
			while (incr ? iterator.moveNext() : iterator.movePrevious());
		}
		catch (e:ItemPendingError)
		{
			e.addResponder(new ItemResponder(
				selectionPendingResultHandler, selectionPendingFailureHandler,
				new ListBaseSelectionPending(incr, index, stopData, transition,
											 placeHolder,
											 CursorBookmark.CURRENT, 0)));
			
			iteratorValid = false;
		}

		try
		{
			iterator.seek(placeHolder, 0);
			iteratorValid = true;
		}
		catch (e2:ItemPendingError)
		{
			lastSeekPending = new ListBaseSeekPending(placeHolder, 0);
			
			e2.addResponder(new ItemResponder(
				seekPendingResultHandler, seekPendingFailureHandler,
				lastSeekPending));

		}
	}

    /**
     *  Clears the set of selected items and removes all graphics
	 *  depicting the selected state of those items.
	 *
	 *  @param transition <code>true</code> if the graphics should
	 *  have a fadeout effect.
     */
    protected function clearSelected(transition:Boolean = false):void
    {
        for (var uniqueID:String in selectedData)
        {
            var data:Object = selectedData[uniqueID].data;
            
			removeSelectionData(uniqueID);
            
			var item:IListItemRenderer = visibleData[itemToUID(data)];
            if (item)
                 drawItem(item, false, uniqueID == highlightUID, false, transition);
        }

        clearSelectionData();

        _selectedIndex = -1;
        _selectedItem = null;

        caretIndex = -1;
        anchorIndex = -1;

		caretBookmark = null;
		anchorBookmark = null;
    }

    /**
	 *  Moves the selection in a horizontal direction in response
	 *  to the user selecting items using the left-arrow or right-arrow
	 *  keys and modifiers such as  the Shift and Ctrl keys.  This method
	 *  might change the <code>horizontalScrollPosition</code>, 
	 *  <code>verticalScrollPosition</code>, and <code>caretIndex</code>
	 *  properties, and call the <code>finishKeySelection()</code>method
	 *  to update the selection.
	 *
	 *  <p>Not implemented in ListBase because the default list
	 *  is single column and therefore doesn't scroll horizontally.</p>
	 *
	 *  @param code The key that was pressed (e.g. Keyboard.LEFT)
	 *  @param shiftKey <code>true</code> if the shift key was held down when
	 *  the keyboard key was pressed.
	 *  @param ctrlKey <code>true</code> if the ctrl key was held down when
	 *  the keyboard key was pressed
     */
    protected function moveSelectionHorizontally(code:uint, shiftKey:Boolean,
												 ctrlKey:Boolean):void
    {
        return;
    }

    /**
	 *  Moves the selection in a vertical direction in response
	 *  to the user selecting items using the up-arrow or down-arrow
	 *  Keys and modifiers such as the Shift and Ctrl keys.  This method
	 *  might change the <code>horizontalScrollPosition</code>, 
	 *  <code>verticalScrollPosition</code>, and <code>caretIndex</code>
	 *  properties, and call the <code>finishKeySelection()</code>method
	 *  to update the selection
	 *
	 *  @param code The key that was pressed (e.g. Keyboard.DOWN)
	 *  @param shiftKey <code>true</code> if the shift key was held down when
	 *  the keyboard key was pressed.
	 *  @param ctrlKey <code>true</code> if the ctrl key was held down when
	 *  the keyboard key was pressed
     */
    protected function moveSelectionVertically(code:uint, shiftKey:Boolean,
											   ctrlKey:Boolean):void
    {
		var newVerticalScrollPosition:Number;
        var listItem:IListItemRenderer;
        var uid:String;
        var len:int;
        var bSelChanged:Boolean = false;

        showCaret = true;

        var rowCount:int = listItems.length;
        var partialRow:int = (rowInfo[rowCount-1].y + rowInfo[rowCount-1].height >
                                  listContent.height) ? 1 : 0;
		var bUpdateVerticalScrollPosition:Boolean = false;
		bSelectItem = false;

        switch (code)
        {
			case Keyboard.UP:
			{
				if (caretIndex > 0)
				{
					caretIndex--;
					bUpdateVerticalScrollPosition = true;
					bSelectItem = true;
				}
				break;
			}

			case Keyboard.DOWN:
			{
				if (caretIndex < collection.length - 1)
				{
					caretIndex++;
					bUpdateVerticalScrollPosition = true;
					bSelectItem = true;
				}
				else if ((caretIndex == collection.length - 1) && partialRow)
				{
					if (verticalScrollPosition < maxVerticalScrollPosition)
						newVerticalScrollPosition = verticalScrollPosition + 1;
				}
				break;
			}

			case Keyboard.PAGE_UP:
			{
				if (caretIndex < lockedRowCount)
				{
					newVerticalScrollPosition = 0;
					caretIndex = 0;
				}
				// if the caret is on-screen, but not at the top row
				// just move the caret to the top row
				else if (caretIndex > verticalScrollPosition + lockedRowCount &&
					caretIndex < verticalScrollPosition + rowCount)
				{
					caretIndex = verticalScrollPosition + lockedRowCount;
				}
				else
				{
					// paging up is really hard because we don't know how many
					// rows to move because of variable row height.  We would have
					// to double-buffer a previous screen in order to get this exact
					// so we just guess for now based on current rowCount
					caretIndex = Math.max(caretIndex - rowCount + lockedRowCount, 0);
					newVerticalScrollPosition = Math.max(caretIndex - lockedRowCount,0)
				}
				bSelectItem = true;
				break;
			}

			case Keyboard.PAGE_DOWN:
			{
				if (caretIndex < lockedRowCount)
				{
					newVerticalScrollPosition = 0;
				}
				// if the caret is on-screen, but not at the bottom row
				// just move the caret to the bottom row (not partial row)
				else if (caretIndex >= verticalScrollPosition + lockedRowCount &&
					caretIndex < verticalScrollPosition + rowCount - partialRow - 1)
				{
				}
				else
				{
					newVerticalScrollPosition = Math.min(caretIndex - lockedRowCount, maxVerticalScrollPosition);
				}
				bSelectItem = true;
				break;
			}

			case Keyboard.HOME:
			{
				if (caretIndex > 0)
				{
					caretIndex = 0;
					bSelectItem = true;
					newVerticalScrollPosition = 0;
				}
				break;
			}

			case Keyboard.END:
			{
				if (caretIndex < collection.length - 1)
				{
					caretIndex = collection.length - 1;
					bSelectItem = true;
					newVerticalScrollPosition = maxVerticalScrollPosition;
				}
				break;
			}
        }

		if (bUpdateVerticalScrollPosition)
		{
            if (caretIndex < lockedRowCount)
            	newVerticalScrollPosition = 0;
            else if (caretIndex < verticalScrollPosition + lockedRowCount)
                newVerticalScrollPosition = caretIndex - lockedRowCount;
            else if (caretIndex >= verticalScrollPosition + rowCount - partialRow)
                newVerticalScrollPosition = Math.min(maxVerticalScrollPosition, caretIndex - rowCount + partialRow + 1);
		}

		if (!isNaN(newVerticalScrollPosition))
		{
			if (verticalScrollPosition != newVerticalScrollPosition)
			{
				var se:ScrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
				se.detail = ScrollEventDetail.THUMB_POSITION;
				se.direction = ScrollEventDirection.VERTICAL;
				se.delta = newVerticalScrollPosition - verticalScrollPosition;
				se.position = newVerticalScrollPosition;
				verticalScrollPosition = newVerticalScrollPosition;
				dispatchEvent(se);
			}
			
			// bail if we page faulted
			if (!iteratorValid)
			{
				keySelectionPending = true;
				return;
			}
		}

		bShiftKey = shiftKey;
		bCtrlKey = ctrlKey;

		lastKey = code;

		finishKeySelection();
	}

	/**
	 *  Sets selected items based on the <code>caretIndex</code> and 
	 *  <code>anchorIndex</code> properties.  
	 *  Called by the keyboard selection handlers
	 *  and by the <code>updateDisplayList</code> method in case the 
	 *  keyboard selection handler
	 *  got a page fault while scrolling to get more items.
	 */
	protected function finishKeySelection():void
	{
        var uid:String;
        var rowCount:int = listItems.length;
        var partialRow:int = (rowInfo[rowCount-1].y + rowInfo[rowCount-1].height >
                                  listContent.height) ? 1 : 0;

		if (lastKey == Keyboard.PAGE_DOWN)
		{
			// set caret to last full row of new screen
			caretIndex = Math.min(verticalScrollPosition + rowCount - partialRow - 1,
								  collection.length - 1);
		}

        var listItem:IListItemRenderer;
		var bSelChanged:Boolean = false;

		if (bSelectItem && caretIndex - verticalScrollPosition >= 0)
		{
            if (caretIndex - verticalScrollPosition > listItems.length - 1)
            	caretIndex = listItems.length - 1 + verticalScrollPosition;

            listItem = listItems[caretIndex - verticalScrollPosition][0];
            if (listItem)
            {
				uid = itemToUID(listItem.data);
				listItem = visibleData[uid];
				if (!bCtrlKey)
				{
					selectItem(listItem, bShiftKey, bCtrlKey);
					bSelChanged = true;
				}
				if (bCtrlKey)
				{
					drawItem(listItem, selectedData[uid] != null, uid == highlightUID, true);
				}
            }
		}

        if (bSelChanged)
            dispatchEvent(new Event("change"));
    }

    /**
     *  @private
	 */
	mx_internal function commitSelectedIndex(value:int):void
	{
        if (value != -1)
        {
            value = Math.min(value, collection.length - 1);
            var bookmark:CursorBookmark = iterator.bookmark;
            var len:int = value - scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
            iterator.seek(CursorBookmark.CURRENT, len);
            var data:Object = iterator.current;
			var selectedBookmark:CursorBookmark = iterator.bookmark;
            var uid:String = itemToUID(data);
            iterator.seek(bookmark, 0);
            if (!selectedData[uid])
            {
                if (visibleData[uid])
                    selectItem(visibleData[uid], false, false);
                else
                {
                    clearSelected();
                    addSelectionData(uid, new ListBaseSelectionData(data, value, approximate));
                    _selectedIndex = value;
                    caretIndex = value;
					caretBookmark = selectedBookmark;
                    anchorIndex = value;
					anchorBookmark = selectedBookmark;
                    _selectedItem = data;
                }
            }
        }
        else
        {
            clearSelected();
        }

        dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
    }

	/**
	 *  @private
	 */
	mx_internal function commitSelectedIndices(indices:Array):void
	{
        // trace("setting indices");
        clearSelected();

		try
		{
			collectionIterator.seek(CursorBookmark.FIRST, 0);
		}
		catch (e:ItemPendingError)
		{
			e.addResponder(new ItemResponder(selectionIndicesPendingResultHandler, selectionIndicesPendingFailureHandler,
													new ListBaseSelectionDataPending(true, 0, indices, CursorBookmark.FIRST, 0)));
			return;
		}

		setSelectionIndicesLoop(0, indices, true);
	}

	/**
     *  @private
     */
	private function setSelectionIndicesLoop(index:int, indices:Array, firstTime:Boolean = false):void
	{
        while (indices.length)
        {
            if (index != indices[0])
			{
				try
				{
					collectionIterator.seek(CursorBookmark.CURRENT, indices[0] - index);
				}
				catch (e:ItemPendingError)
				{
					e.addResponder(new ItemResponder(selectionIndicesPendingResultHandler, selectionIndicesPendingFailureHandler,
												new ListBaseSelectionDataPending(firstTime, index, indices, CursorBookmark.CURRENT, indices[0] - index)));
					return;
				}

			}
            index = indices[0];
			indices.shift()

            var data:Object = collectionIterator.current;
            if (firstTime)
			{
				_selectedIndex = index;
                _selectedItem = data;
				firstTime = false;
			}
            addSelectionData(itemToUID(data), new ListBaseSelectionData(data, index, false));
            // trace("uid = " + itemToUID(data));
        }

		if (initialized)
			updateList();
    }

	/**
     *  @private
     */
    private function commitSelectedItem(data:Object, clearFirst:Boolean = true):void
	{
        if (clearFirst)
            clearSelected();
		commitSelectedItems([data]);
    }

	/**
	 *  @private
	 */
	private function commitSelectedItems(items:Array):void
	{
        clearSelected();

		var useFind:Boolean = collection.sort != null;

		try
		{
			collectionIterator.seek(CursorBookmark.FIRST, 0);
		}
		catch (e:ItemPendingError)
		{
			e.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
													new ListBaseSelectionDataPending(useFind, 0, items, null, 0)));
			return;
		}

		setSelectionDataLoop(items, 0, useFind);
	}

	/**
     *  @private
     */
	private function setSelectionDataLoop(items:Array, index:int, useFind:Boolean = true):void
	{
		var uid:String;

		if (useFind)
		{
			while (items.length)
			{
				var item:Object = items.pop();
				uid = itemToUID(item);

				try
				{
					collectionIterator.findAny(item);
				}
				catch (e1:ItemPendingError)
				{
					items.push(item);
					e1.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
															new ListBaseSelectionDataPending(useFind, 0, items, null, 0)));
					return;
				}
				var bookmark:CursorBookmark = collectionIterator.bookmark;
				var index:int = bookmark.getViewIndex();
				if (index >= 0)
					addSelectionData(uid, new ListBaseSelectionData(item, index, true));
				else
				{
					try
					{
						collectionIterator.seek(CursorBookmark.FIRST, 0);
					}
					catch (e2:ItemPendingError)
					{
						e2.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
																new ListBaseSelectionDataPending(false, 0, items, CursorBookmark.FIRST, 0)));
						return;
					}

					// collection doesn't support indexes from bookmarks so
					// try again w/o using bookmarks
					setSelectionDataLoop(items, 0, false);
					return;
				}
				if (items.length == 0)
					_selectedIndex = index;
			}
		}
		else
		{
			while (items.length && !collectionIterator.afterLast)
			{
				var len:int = items.length;
				var data:Object = collectionIterator.current;
				for (var i:int = 0; i < len; i++)
				{
					if (data == items[i])
					{
						uid = itemToUID(data);
						addSelectionData(uid, new ListBaseSelectionData(data, index, false));
						items.splice(i, 1);
						if (items.length == 0)
						{
							_selectedIndex = index;
							_selectedItem = data;
						}
						break;
					}
				}
				try
				{
					collectionIterator.moveNext();
					index++;
				}
				catch (e2:ItemPendingError)
				{
					e2.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
															new ListBaseSelectionDataPending(false, index, items, CursorBookmark.CURRENT, 1)));
					return;
				}
			}
			if (items.length)
				trace("didn't find all selected items");
		}

		if (initialized)
			updateList();
    }

	/**
     *  @private
     */
    private function clearSelectionData():void
    {
        selectedData = {};
        firstSelectionData = null;
    }

	/**
     *  @private
     */
    private function addSelectionData(uid:String, selectionData:ListBaseSelectionData):void
    {
	    if (firstSelectionData != null)
	        firstSelectionData.prevSelectionData = selectionData;
	    selectionData.nextSelectionData = firstSelectionData;
	    firstSelectionData = selectionData;
	    
	    selectedData[uid] = selectionData;
    }

	/**
     *  @private
     */
    private function removeSelectionData(uid:String):void
    {
        var curSelectionData:ListBaseSelectionData = selectedData[uid];
        
        if (firstSelectionData == curSelectionData)
            firstSelectionData = curSelectionData.nextSelectionData;
        
        if (curSelectionData.prevSelectionData != null)
            curSelectionData.prevSelectionData.nextSelectionData = curSelectionData.nextSelectionData;

        if (curSelectionData.nextSelectionData != null)
            curSelectionData.nextSelectionData.prevSelectionData = curSelectionData.prevSelectionData;
        
        delete selectedData[uid];
    }

	/**
	 *  Sets up the effect for applying the selection indicator.
	 *  The default is a basic alpha tween.
	 *
	 *  @param indicator A Sprite that contains the graphics depicting selection
	 *  @param uid The UID of the item being selected which can be used to index
	 *  into a table and track more than one selection effect
	 *  @param itemRenderer The item renderer that is being shown as selected
	 */
	protected function applySelectionEffect(indicator:Sprite, uid:String,
											itemRenderer:IListItemRenderer):void
	{
		var selectionDuration:Number =
			getStyle("selectionDuration");

		if (selectionDuration != 0)
		{
			indicator.alpha = 0;

			selectionTweens[uid] =
				new Tween(indicator, 0, 1, selectionDuration, 5);

			selectionTweens[uid].addEventListener(TweenEvent.TWEEN_UPDATE,
												  selectionTween_updateHandler);

			selectionTweens[uid].addEventListener(TweenEvent.TWEEN_END,
												  selectionTween_endHandler);

			selectionTweens[uid].setTweenHandlers(onSelectionTweenUpdate,
												  onSelectionTweenUpdate);

			var selectionEasingFunction:Function =
				getStyle("selectionEasingFunction") as Function;
			if (selectionEasingFunction != null)
				selectionTweens[uid].easingFunction = selectionEasingFunction;
		}
	}

    /**
     *  @private
     */
    private function onSelectionTweenUpdate(value:Number):void
    {
    }

    /**
     *  Makes a copy of the selected items in the order they were
	 *  selected.
	 *
	 *  @param useDataField <code>true</code> if the array should
	 *  be filled with the actual items or <code>false</code>
	 *  if the array should be filled with the indexes of the items
	 *
	 *  @return array of selected items
     */
    protected function copySelectedItems(useDataField:Boolean = true):Array
    {
        var tmp:Array = [];

        var curSelectionData:ListBaseSelectionData = firstSelectionData;
        while (curSelectionData != null)
        {
            if (useDataField)
                tmp.push(curSelectionData.data);
            else
                tmp.push(curSelectionData.index);
            
            curSelectionData = curSelectionData.nextSelectionData;
        }
        
        return tmp;
    }

 	//--------------------------------------------------------------------------
	//
	//  Methods: Scrolling
	//
	//--------------------------------------------------------------------------

	/**
	 *  Returns the data provider index for the item at the first visible
	 *  row and column for the given scroll positions.
	 *
	 *  @param horizontalScrollPosition The <code>horizontalScrollPosition</code>
	 *         property value corresponding to the scroll position.
	 *  @param verticalScrollPosition The <code>verticalScrollPosition</code>
	 *         property value corresponding to the scroll position.
	 *
	 *  @return The data provider index
	 */
	protected function scrollPositionToIndex(horizontalScrollPosition:int,
											 verticalScrollPosition:int):int
	{
		return iterator ? verticalScrollPosition : -1;
	}

    /**
     *  Ensures that the data provider item at the given index is visible.
	 *  If the item is visible, the <code>verticalScrollPosition</code>
	 *  property is left unchanged even if the item is not the first visible
	 *  item. If the item is not currently visible, the 
	 *  <code>verticalScrollPosition</code>
	 *  property is changed make the item the first visible item, unless there
	 *  aren't enough rows to do so because the 
	 *  <code>verticalScrollPosition</code> value is limited by the 
	 *  <code>maxVerticalScrollPosition</code> property.
	 *
	 *  @return <code>true</code> if <code>verticalScrollPosition</code> changed
     */
	public function scrollToIndex(index:int):Boolean
	{
		var newVPos:int;

		if (index >= verticalScrollPosition + listItems.length - lockedRowCount || index < verticalScrollPosition)
		{
			newVPos = Math.min(index, maxVerticalScrollPosition);
			verticalScrollPosition = newVPos;
			return true;
		}
		return false;
	}

    /**
	 *  Adjusts the renderers in response to a change
	 *  in scroll position.
	 *
	 *  <p>The list classes attempt to optimize scrolling
	 *  when the scroll position has changed by less than
	 *  the number of visible rows.  In that situation
	 *  some rows are unchanged and just need to be moved,
	 *  other rows are removed and then new rows are added.
	 *  If the scroll position changes too much, all old rows are removed
	 *  and new rows are added by calling the <code>makeRowsAndColumns()</code>
	 *  method for the entire viewable area.</p>
	 *
	 *  @param pos The new scroll position.
	 *  @param deltaPos The change in position.  It is always
	 *  a positive number.
	 *  @param scrollUp <code>true</code> if scroll position
	 *  is getting smaller.
     */
    protected function scrollVertically(pos:int, deltaPos:int,
										scrollUp:Boolean):void
    {
        // trace("scrollVertically " + pos);

        var i:int;
        var j:int;
        var r:IListItemRenderer;
        var item:IListItemRenderer;

        var numRows:int;
        var numCols:int;
        var uid:String;

        var visibleY:Number;
        var curY:Number;

        var rowCount:int = rowInfo.length;
        var columnCount:int = listItems[0].length;
        var cursorPos:CursorBookmark;

        var moveBlockDistance:Number = 0;
        visibleY = (lockedRowCount > 0) ? rowInfo[lockedRowCount - 1].y + rowInfo[lockedRowCount - 1].height : 0;
        if (scrollUp)
        {
            // find first fully visible row not spanning onto the screen;
            // trace("visibleY = " + visibleY);
            for (i = lockedRowCount; i < rowCount; i++)
            {
                if (rowInfo[i].y >= visibleY)
                    break;
            }

            var startRow:int = i;

            // measure how far we have to move by measuring each row
            for (i; i < deltaPos + startRow; i++)
            {
                // after we shift the items, see if any are still visible
                moveBlockDistance += rowInfo[i].height;
       			try
				{
					iterator.moveNext();
				}
				catch (e:ItemPendingError)
				{
					lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos)
					e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
														lastSeekPending));
					iteratorValid = false;
					// trace("itemPending in ScrollVertically");
					return;
				}
			}

            // trace("moveBlockDistance = " + moveBlockDistance);
            deltaPos = 0;

            //  shift rows upward and toss fully invisible ones.
            for (i = lockedRowCount; i < rowCount; i++)
            {
				numCols = listItems[i].length;
                var bVisible:Boolean = false;
                // after we shift the items, see if any are still visible
                for (j = 0; j < columnCount && j < numCols; j++)
                {
                    r = listItems[i][j];
                    r.move(r.x, r.y - moveBlockDistance);
                    if (r.data && r is IDropInListItemRenderer)
                        IDropInListItemRenderer(r).listData.rowIndex = i;
                    rowMap[r.name].rowIndex = i;
                }
                rowInfo[i].y -= moveBlockDistance;
                if (rowInfo[i].y + rowInfo[i].height > visibleY)
                    bVisible = true;
                uid = rowInfo[i].uid;
                // if not toss them
                if (!bVisible)
                {
                    removeIndicators(uid);
                    for (j = 0; j < columnCount && j < numCols; j++)
                    {
                        r = listItems[i][j];
                        if (r.data)
                            delete visibleData[rowMap[r.name].uid];
                        addToFreeItemRenderers(r);
                    }
                    deltaPos++;
                }
                else
                {
					if (uid)
					{
						if (selectionIndicators[uid])
							selectionIndicators[uid].y -= moveBlockDistance;
						if (highlightUID == uid)
							highlightIndicator.y -= moveBlockDistance;
						if (caretUID == uid)
							caretIndicator.y -= moveBlockDistance;
					}
                }
            }
            // trace("tossed " + deltaPos + " " + rowCount);
            // deltaPos is now the number of rows we tossed
            if (deltaPos)
            {
                for (i = lockedRowCount + deltaPos; i < rowCount; i++)
                {
					numCols = listItems[i].length;
                    for (j = 0; j < columnCount && j < numCols; j++)
                    {
                        // trace("compacting " + i + " " + j);
                        // compact the array
                        r = listItems[i][j];
                        if (r.data && r is IDropInListItemRenderer)
                            IDropInListItemRenderer(r).listData.rowIndex = i - deltaPos;
                        rowMap[r.name].rowIndex = i - deltaPos;
                        listItems[i - deltaPos][j] = r;
                    }
					// if no columns, make destination row empty.  Normally this is filled in by
					// makeRowsAndColumns, but if it page faults it won't fill it in
					// but the item in [i - deltapos] is already on the free list
					if (!numCols)
						listItems[i - deltaPos].splice(0);
                    rowInfo[i - deltaPos] = rowInfo[i];
                }
                listItems.splice(rowCount - deltaPos);
                rowInfo.splice(rowCount - deltaPos);
                // trace("listItems.length = " + listItems.length);
            }
            curY = rowInfo[rowCount - deltaPos - 1].y + rowInfo[rowCount - deltaPos - 1].height;

            cursorPos = iterator.bookmark;
			try
			{
				iterator.seek(CursorBookmark.CURRENT, rowCount - lockedRowCount - deltaPos);
			}
			catch (e1:ItemPendingError)
			{
				// trace("IPE in scrollVertically");
				lastSeekPending = new ListBaseSeekPending(cursorPos, 0)
				e1.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
													lastSeekPending));
				iteratorValid = false;
				// we don't do anything here and will repaint when the rows arrive
			}

            // fill it in
            makeRowsAndColumns(0, curY, listContent.width, listContent.height, 0, rowCount - deltaPos);
            iterator.seek(cursorPos, 0);
        }
        else
        {
            // scrolling down is different because rows are locked to top.
			// instead of measuring how much space we lost, we make the rows requested
			// and then toss as many (including 0) rows as needed to make room for the
			// new rows
            // copy the old rows
			curY = 0;
			if (lockedRowCount > 0)
				curY = rowInfo[lockedRowCount - 1].y + rowInfo[lockedRowCount - 1].height;

			// insert slots to be filled by new rows
			for (i = 0; i < deltaPos; i++)
			{
				listItems.splice(lockedRowCount, 0, null);
				rowInfo.splice(lockedRowCount, 0, null);
			}

			try
			{
				iterator.seek(CursorBookmark.CURRENT, -deltaPos);
			}
			catch (e2:ItemPendingError)
			{
				lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, -deltaPos)
				e2.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
														lastSeekPending));
				iteratorValid = false;
			}
            cursorPos = iterator.bookmark;

            var actual:Point = makeRowsAndColumns(0, curY, listContent.width, listContent.height, 0, lockedRowCount, true, deltaPos);
            // trace("made " + actual.y);
            iterator.seek(cursorPos, 0);

			if (actual.y == 0)
			{
				// no more rows, set verticalScrollPosition to 0, restore the rows and leave
				verticalScrollPosition = 0;
				rowInfo.splice(lockedRowCount, deltaPos);
				listItems.splice(lockedRowCount, deltaPos);
			}

            // measure how far we have to move by measuring each new row
            for (i = 0; i < actual.y; i++)
            {
                moveBlockDistance += rowInfo[lockedRowCount + i].height;
            }
            // trace("moveBlockDistance = " + moveBlockDistance);

            var row:Array;
            var rowData:Object;

            // trace("curY = " + curY);
            var deltaY:Number;
			curY += moveBlockDistance;

            // trace("curY = " + curY);
            // fix up positions of old rows and delete any that fell off bottom
            for (i = lockedRowCount + actual.y; i < listItems.length; i++)
            {
                row = listItems[i];
                rowData = rowInfo[i];
                var deleteRow:Boolean = false;
                deltaY = curY - rowData.y;
                // trace("deltaY = " + deltaY + " curY = " + curY + " newRowIndex = " + newRowIndex);
                rowData.y = curY;
				if (row.length)
				{
					for (j = 0; j < row.length; j++)
					{
						item = row[j];
						item.move(item.x, item.y + deltaY);
						if (item.y >= listContent.height)
						{
							deleteRow = true;
						}
						if (!deleteRow)
						{
							rowMap[item.name].rowIndex += deltaPos;
						}
					}
				}
				else
				{
					if (rowData.y >= listContent.height)
						deleteRow = true;
				}
                uid = rowInfo[i].uid;
                if (deleteRow)
                {
                    var oldRow:Array = listItems[i];
                    if (oldRow.length && oldRow[0].data)
                    {
                        removeIndicators(uid);
                    }
                    for (j = 0; j < oldRow.length; j++)
                    {
                        if (oldRow[j].data)
                            delete visibleData[uid];
                        addToFreeItemRenderers(oldRow[j]);
                    }
					listItems.splice(i, 1);
                    rowInfo.splice(i, 1);
					i--;	// backup one cuz we deleted one
                }
				if (uid)
				{
					if (selectionIndicators[uid])
						selectionIndicators[uid].y = curY;
					if (highlightUID == uid)
						highlightIndicator.y = curY;
					if (caretUID == uid)
						caretIndicator.y = curY;
				}
                curY += rowData.height;
            }
            rowCount = listItems.length;
        }
    }

    /**
	 *  Adjusts the renderers in response to a change
	 *  in scroll position.
	 *
	 *  <p>The list classes attempt to optimize scrolling
	 *  when the scroll position has changed by less than
	 *  the number of visible rows.  In that situation
	 *  some rows are unchanged and just need to be moved,
	 *  other rows are removed and then new rows are added.
	 *  If the scroll position changes too much, all old rows are removed
	 *  and new rows are added by calling the <code>makeRowsAndColumns()</code>
	 *  method for the entire viewable area.</p>
	 *
	 *  <p>Not implemented in ListBase because the default list
	 *  is single column and therefore doesn't scroll horizontally.</p>
	 *
	 *  @param pos The new scroll position.
	 *  @param deltaPos The change in position.  It is always
	 *  a positive number.
	 *  @param scrollUp <code>true</code> if scroll position
	 *  is getting smaller.
     */
    protected function scrollHorizontally(pos:int, deltaPos:int, scrollUp:Boolean):void
    {
        // update visible columns

        // translate vertical logic here
    }

    /**
     *  Configures the ScrollBars based on the number of rows and columns and
	 *  viewable rows and columns.
	 *  This method is called from the <code>updateDisplayList()</code> method
	 *  after the rows and columns have been updated.
	 *  The method should figures out what parameters to pass into the 
	 *  <code>setScrollBarProperties()</code> to properly set the ScrollBars up.
     */
    protected function configureScrollBars():void
    {
    }

    /**
     *  Interval function that scrolls the list up or down
	 *  if the mouse goes above or below the list.
     */
	protected function dragScroll():void
	{
		var slop:Number = 0;
		var scrollInterval:Number;
		var oldPosition:Number;
		var d:Number;
		var scrollEvent:ScrollEvent;

		const minScrollInterval:Number = 30;

		if (DragManager.isDragging)
		{
			slop = viewMetrics.top
				+ (variableRowHeight ? getStyle("fontSize") / 4 : rowHeight);
		}

		clearInterval(dragScrollingInterval);

		if (mouseY < slop)
		{
			oldPosition = verticalScrollPosition;
			verticalScrollPosition = Math.max(0, oldPosition - 1);
			if (DragManager.isDragging)
			{
				scrollInterval = 100;
			}
			else
			{
				d = Math.min(0 - mouseY - 30, 0);
				// quadratic relation between distance and scroll speed
				scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
			}

			dragScrollingInterval = setInterval(dragScroll, scrollInterval);

			if (oldPosition != verticalScrollPosition)
			{
				scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
				scrollEvent.direction = ScrollEventDirection.VERTICAL;
				scrollEvent.position = verticalScrollPosition;
				scrollEvent.delta = verticalScrollPosition - oldPosition;
	            dispatchEvent(scrollEvent);
			}
		}
		else if (mouseY > (unscaledHeight - slop))
		{
			oldPosition = verticalScrollPosition;
			verticalScrollPosition = Math.min(maxVerticalScrollPosition, verticalScrollPosition + 1);
			if (DragManager.isDragging)
			{
				scrollInterval = 100;
			}
			else
			{
				d = Math.min(mouseY - unscaledHeight - 30, 0);
				scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
			}

			dragScrollingInterval = setInterval(dragScroll, scrollInterval);

			if (oldPosition != verticalScrollPosition)
			{
				scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
				scrollEvent.direction = ScrollEventDirection.VERTICAL;
				scrollEvent.position = verticalScrollPosition;
				scrollEvent.delta = verticalScrollPosition - oldPosition;
				dispatchEvent(scrollEvent);
			}
		}
		else
		{
			dragScrollingInterval = setInterval(dragScroll, 15);
		}
	}

    /**
     *  @private
     *  Stop the drag scrolling callback.
     */
    mx_internal function resetDragScrolling():void
    {
        if (dragScrollingInterval != 0)
        {
            clearInterval(dragScrollingInterval);
            dragScrollingInterval = 0;
        }
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Drag and drop
	//
	//--------------------------------------------------------------------------

    /**
	 *  Adds the selected items to the DragSource object as part of a
	 *  drag-and-drop operation.
	 *  Override this method to add other data to the drag source.
	 * 
	 * @param ds The DragSource object to which to add the data.
     */
    protected function addDragData(ds:Object):void // actually a DragSource
    {
        ds.addHandler(copySelectedItems, "items");
    }

    /**
     *  Returns the index where the dropped items should be added 
	 *  to the drop target.
	 *
	 *  @param event A DragEvent that contains information about
	 *  the position of the mouse.  If <code>null</code> the
	 *  method should return the <code>dropIndex</code> value from the 
	 *  last valid event.
	 *
	 *  @return Index where the dropped items should be added.
     */
    public function calculateDropIndex(event:DragEvent = null):int
    {
        if (event)
		{
			var item:IListItemRenderer;
			var pt:Point = new Point(event.localX, event.localY);
			pt = DisplayObject(event.target).localToGlobal(pt);
			pt = listContent.globalToLocal(pt);

			var rc:int = listItems.length;
			for (var i:int = 0; i < rc; i++)
			{
				if (rowInfo[i].y <= pt.y && pt.y <= rowInfo[i].y + rowInfo[i].height)
				{
					item = listItems[i][0];
					break;
				}
			}

			if (item)
				lastDropIndex = itemRendererToIndex(item);
			else
				lastDropIndex = collection ? collection.length : 0;
		}

        return lastDropIndex;
    }

	/**
	 *  Calculates the y position of the drop indicator 
	 *  when performing a drag-and-drop operation.
	 *
	 *  @param rowCount The number of visible rows in the control.
	 *
	 *  @param rowNum The row number in the control where the drop indicator should appear.
	 *
	 *  @return The y axis coordinate of the drop indicator.
	 */
	protected function calculateDropIndicatorY(rowCount:Number,
											   rowNum:int):Number
	{
		var i:int;
		var yy:Number = 0;

		if (rowCount && listItems[rowNum].length && listItems[rowNum][0])
		{
		   return listItems[rowNum][0].y - 1
		}

		for (i = 0; i < rowCount; i++)
		{
			if (listItems[i].length)
				yy += rowInfo[i].height;
			else
				break;
		}
		return yy;
	}
	
    /**
     *  Displays a drop indicator under the mouse pointer to indicate that a
     *  drag and drop operation is allowed and where the items will
	 *  be dropped.
	 *
	 *  @param event A DragEvent object that contains information as to where
	 *  the mouse is.
     */
    public function showDropFeedback(event:DragEvent):void
    {
        if (!dropIndicator)
        {
            var dropIndicatorClass:Class = getStyle("dropIndicatorSkin");
            if (!dropIndicatorClass)
                dropIndicatorClass = ListDropIndicator;
            dropIndicator = IFlexDisplayObject(new dropIndicatorClass());

            var vm:EdgeMetrics = viewMetrics;

            drawFocus(true);

            dropIndicator.x = 2;
            dropIndicator.setActualSize(listContent.width - 4, 4);
            dropIndicator.visible = true;
            listContent.addChild(DisplayObject(dropIndicator));

            if (collection)
                dragScroll();
        }

        var rowNum:Number = calculateDropIndex(event);
        if (rowNum >= lockedRowCount)
         	rowNum -= verticalScrollPosition;

		var rc:Number = listItems.length;
        if (rowNum >= rc)
            rowNum = rc - 1;
        
        if (rowNum < 0)
        	rowNum = 0;

        dropIndicator.y = calculateDropIndicatorY(rc, rowNum);
    }

    /**
     *  Hides the drop indicator under the mouse pointer that indicates that a
     *  drag and drop operation is allowed.
	 *
	 *  @param event A DragEvent object that contains information about the
	 *  mouse location.
     */
    public function hideDropFeedback(event:DragEvent):void
    {
        if (dropIndicator)
        {
            listContent.removeChild(DisplayObject(dropIndicator));
            dropIndicator = null;
            drawFocus(false);
            if (dragScrollingInterval != 0)
            {
                clearInterval(dragScrollingInterval);
                dragScrollingInterval = 0;
            }
        }
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Support for pending data
	//
	//--------------------------------------------------------------------------

    /**
     *  The default failure handler when a seek fails due to a page fault.
     */
	protected function seekPendingFailureHandler(data:Object,
												 info:ListBaseSeekPending):void
	{
	}

    /**
     *  The default result handler when a seek fails due to a page fault.
	 *  This method checks to see if it has the most recent page fault result:
	 *  if not it simply exits; if it does, it sets the iterator to
	 *  the correct position.
     */
	protected function seekPendingResultHandler(data:Object,
												info:ListBaseSeekPending):void
	{
		// trace("seekPendingResultHandler", this);

		if (info != lastSeekPending)
		{
			return;
		}

		lastSeekPending = null;

		iteratorValid = true;
		iterator.seek(info.bookmark, info.offset);
		if (bSortItemPending)
		{
			bSortItemPending = false;
			adjustAfterSort();
		}
        itemsSizeChanged = true;
        invalidateDisplayList();
	}

    /**
     *  @private
     */
	private function findPendingFailureHandler(data:Object,
											   info:ListBaseFindPending):void
	{
	}

    /**
     *  @private
     */
	private function findPendingResultHandler(data:Object,
											  info:ListBaseFindPending):void
	{
		// trace("findPendingResultHandler", this);
		iterator.seek(info.bookmark, info.offset);
		findStringLoop(info.searchString, info.startingBookmark, info.currentIndex, info.stopIndex);
	}

    /**
     *  @private
     */
	private function selectionPendingFailureHandler(
									data:Object,
									info:ListBaseSelectionPending):void
	{
	}

    /**
     *  @private
     */
	private function selectionPendingResultHandler(
									data:Object,
									info:ListBaseSelectionPending):void
	{
		// trace("selectionPendingResultHandler", this);
		iterator.seek(info.bookmark, info.offset);
		shiftSelectionLoop(info.incrementing, info.index, info.stopData,
						   info.transition, info.placeHolder);
	}

    /**
     *  @private
     */
	private function selectionDataPendingFailureHandler(
									data:Object,
									info:ListBaseSelectionDataPending):void
	{
	}

    /**
     *  @private
     */
	private function selectionDataPendingResultHandler(
									data:Object,
									info:ListBaseSelectionDataPending):void
	{
		// trace("selectionDataPendingResultHandler", this);
		if (info.bookmark)
			iterator.seek(info.bookmark, info.offset);
		setSelectionDataLoop(info.items, info.index, info.useFind);
	}

    /**
     *  @private
     */
	private function selectionIndicesPendingFailureHandler(
									data:Object,
									info:ListBaseSelectionDataPending):void
	{
	}

    /**
     *  @private
     */
	private function selectionIndicesPendingResultHandler(
									data:Object,
									info:ListBaseSelectionDataPending):void
	{
		// trace("selectionIndicesPendingResultHandler", this);
		if (info.bookmark)
			iterator.seek(info.bookmark, info.offset);
		setSelectionIndicesLoop(info.index, info.items, info.useFind);
	}

	//--------------------------------------------------------------------------
	//
	//  Methods: Keyboard lookup
	//
	//--------------------------------------------------------------------------

    /**
     *  Tries to find the next item in the data provider that
	 *  starts with the character in the <code>eventCode</code> parameter.
     *  You can override this to do fancier typeahead lookups.  The search
	 *  starts at the <code>selectedIndex</code> location; if it reaches
	 *  the end of the data provider it starts over from the beginning.
	 *
	 *  @param eventCode The key that was pressed on the keyboard
	 *  @return <code>true</code> if a match was found
     */
    protected function findKey(eventCode:int):Boolean
    {
        var tmpCode:int = eventCode;
        
		return tmpCode >= 33 &&
               tmpCode <= 126 &&
               findString(String.fromCharCode(tmpCode));
    }

    /**
     *  Finds an item in the list based on a string
	 *  and moves the selection to it.  The search
	 *  starts at the <code>selectedIndex</code> location; if it reaches
	 *  the end of the data provider it starts over from the beginning.
	 *
	 *  @param str The string to match
	 *  @return <code>true</code> if a match was found
     */
    public function findString(str:String):Boolean
    {
        if (!collection || collection.length == 0)
			return false;

        var cursorPos:CursorBookmark;
		cursorPos = iterator.bookmark;

        var stopIndex:int = selectedIndex;
		var i:int = stopIndex + 1;	// start at next

		if (selectedIndex == -1)
		{
			try
			{
				iterator.seek(CursorBookmark.FIRST, 0);
			}
			catch (e1:ItemPendingError)
			{
				e1.addResponder(new ItemResponder(
					findPendingResultHandler, findPendingFailureHandler,
					new ListBaseFindPending(str, cursorPos,
					CursorBookmark.FIRST, 0, 0, collection.length)));
				
				iteratorValid = false;
				return false;
			}
			stopIndex = collection.length;
			i = 0;
		}
		else
		{
			try
			{
				iterator.seek(CursorBookmark.FIRST, stopIndex);
			}
			catch (e2:ItemPendingError)
			{
				if (anchorIndex == collection.length - 1)
				{
					e2.addResponder(new ItemResponder(
						findPendingResultHandler, findPendingFailureHandler,
						new ListBaseFindPending(str, cursorPos,
						CursorBookmark.FIRST, 0, 0, collection.length)));
				}
				else
				{
					e2.addResponder(new ItemResponder(
						findPendingResultHandler, findPendingFailureHandler,
						new ListBaseFindPending(str, cursorPos,
						anchorBookmark, 1, anchorIndex + 1, anchorIndex)));
				}
				
				iteratorValid = false;
				return false;
			}

			var bMovedNext:Boolean = false;
			
			// If we ran off the end, go back to beginning.
			try
			{
				bMovedNext = iterator.moveNext();
			}
			catch (e3:ItemPendingError)
			{
				// Assume we don't fault unless there is more data.
				e3.addResponder(new ItemResponder(
					findPendingResultHandler, findPendingFailureHandler,
					new ListBaseFindPending(str, cursorPos,
					anchorBookmark, 1, anchorIndex + 1, anchorIndex)));
				
				iteratorValid = false;
				return false;
			}

			if (!bMovedNext)
			{
				try
				{
					iterator.seek(CursorBookmark.FIRST, 0);
				}
				catch (e4:ItemPendingError)
				{
					e4.addResponder(new ItemResponder(
						findPendingResultHandler, findPendingFailureHandler,
						new ListBaseFindPending(str, cursorPos,
						CursorBookmark.FIRST, 0, 0, collection.length)));
					
					iteratorValid = false;
					return false;
				}
				
				stopIndex = collection.length;
				i = 0;
			}
		}

		return findStringLoop(str, cursorPos, i, stopIndex);
	}

	/**
	 *  @private
	 */
	private function findStringLoop(str:String, cursorPos:CursorBookmark,
									i:int, stopIndex:int):Boolean
	{
        // Search from the current index.
		// Jump back to beginning if we hit the end.
        for (i; i != stopIndex; i++)
        {
            var itmStr:String = itemToLabel(iterator.current);

            itmStr = itmStr.substring(0, str.length);
            if (str == itmStr || str.toUpperCase() == itmStr.toUpperCase())
            {
				iterator.seek(cursorPos, 0);
				scrollToIndex(i);
				commitSelectedIndex(i);
                dispatchEvent(new Event("change"));
            	return true;
            }

			try
			{
	            var more:Boolean = iterator.moveNext();
			}
			catch (e1:ItemPendingError)
			{
				e1.addResponder(new ItemResponder(
					findPendingResultHandler, findPendingFailureHandler,
					new ListBaseFindPending(str, cursorPos,
					CursorBookmark.CURRENT, 1, i + 1, stopIndex)));
				
				iteratorValid = false;
				return false;
			}

            // Start from beginning if we hit the end
            if (!more && stopIndex != collection.length)
            {
                i = -1;
				try
				{
					iterator.seek(CursorBookmark.FIRST, 0);
				}
				catch (e2:ItemPendingError)
				{
					e2.addResponder(new ItemResponder(
						findPendingResultHandler, findPendingFailureHandler,
						new ListBaseFindPending(str, cursorPos,
						CursorBookmark.FIRST, 0, 0, stopIndex)));
					
					iteratorValid = false;
					return false;
				}
			}
        }

	    iterator.seek(cursorPos, 0);
		iteratorValid = true;
        
		return false;
    }

	//--------------------------------------------------------------------------
	//
	//  Methods: Sorting
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private function adjustAfterSort():void
	{
		var i:int = 0;
		for (var p:String in selectedData)
		{
			i++;
		}

		var index:int = anchorBookmark ? anchorBookmark.getViewIndex() : -1;
		if (index >= 0)
		{
			// If only one thing selected, then we're done.
			if (i == 1)
			{
				_selectedIndex = anchorIndex = caretIndex = index;
				var data:ListBaseSelectionData = selectedData[p];
				data.index = index;
				
			}

			var newVerticalScrollPosition:int = indexToRow(index);
			newVerticalScrollPosition =
				Math.min(maxVerticalScrollPosition, newVerticalScrollPosition);
			
			var newHorizontalScrollPosition:int = indexToColumn(index);
			newHorizontalScrollPosition =
				Math.min(maxHorizontalScrollPosition, newHorizontalScrollPosition);
			
			// Prepare to refresh from there.
			var pos:int = scrollPositionToIndex(newHorizontalScrollPosition,
												newVerticalScrollPosition);
			try
			{
				iterator.seek(CursorBookmark.CURRENT, pos - index);
			}
			catch (e:ItemPendingError)
			{
				lastSeekPending = new ListBaseSeekPending(
					CursorBookmark.CURRENT, pos - index)
				
				e.addResponder(new ItemResponder(
					seekPendingResultHandler, seekPendingFailureHandler,
					lastSeekPending));
				
				// trace("IPE in UpdateDisplayList");
				iteratorValid = false;
				return;
			}

			super.verticalScrollPosition = newVerticalScrollPosition;
			super.horizontalScrollPosition = newHorizontalScrollPosition;
		}
		else
		{
			try
			{
				iterator.seek(CursorBookmark.FIRST, verticalScrollPosition);
			}
			catch (e:ItemPendingError)
			{
				lastSeekPending = new ListBaseSeekPending(
					CursorBookmark.FIRST, verticalScrollPosition);

				e.addResponder(new ItemResponder(
					seekPendingResultHandler, seekPendingFailureHandler,
					lastSeekPending));
				
				// trace("IPE in UpdateDisplayList");
				iteratorValid = false;
				return;
			}
		}

		// If there's more than one selection, find their new indices.
		if (i > 1)
			commitSelectedItems(selectedItems);
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden event handlers: UIComponent
	//
	//--------------------------------------------------------------------------

	/**
     *  @private
     */
    override protected function keyDownHandler(event:KeyboardEvent):void
    {
        if (!selectable)
			return;

		if (!iteratorValid)
			return;

		if (!collection)
			return;

        switch (event.keyCode)
        {
			case Keyboard.UP:
			case Keyboard.DOWN:
			{
				moveSelectionVertically(
					event.keyCode, event.shiftKey, event.ctrlKey);
				event.stopPropagation();
				break;
			}

			case Keyboard.LEFT:
			case Keyboard.RIGHT:
			{
				moveSelectionHorizontally(
					event.keyCode, event.shiftKey, event.ctrlKey);
				event.stopPropagation();
				break;
			}

			case Keyboard.END:
			case Keyboard.HOME:
			case Keyboard.PAGE_UP:
			case Keyboard.PAGE_DOWN:
			{
				moveSelectionVertically(
					event.keyCode, event.shiftKey, event.ctrlKey);
				event.stopPropagation();
				break;
			}

			case Keyboard.SPACE:
			{
				if (caretIndex != -1 && ((caretIndex - verticalScrollPosition + lockedRowCount) >= 0) &&
					((caretIndex - verticalScrollPosition + lockedRowCount) < listItems.length))
				{
					var li:IListItemRenderer =
						listItems[caretIndex - verticalScrollPosition + lockedRowCount][0];
					if (selectItem(li, event.shiftKey, event.ctrlKey))
						dispatchEvent(new Event("change"));
				}
				break;
			}

			default:
			{
				if (findKey(event.keyCode))
					event.stopPropagation();
			}
        }
    }

    /**
	 *  Handles mouseWheel events by changing scroll positions.
	 *  This is a copy of the version in ScrollControlBase,
	 *  modified to change the horizontalScrollPosition if the target is runs horizontally
	 *
	 *  @see mx.core.ScrollControlBase#mouseWheelHandler()
     */
    override protected function mouseWheelHandler(event:MouseEvent):void
    {
        if (verticalScrollBar)
        {
            event.stopPropagation();
            var oldPosition:Number = verticalScrollPosition;
            var newPos:int = verticalScrollPosition;
            newPos -= event.delta * verticalScrollBar.lineScrollSize;
            newPos = Math.max(0, Math.min(newPos, verticalScrollBar.maxScrollPosition));
            verticalScrollPosition = newPos;

			if (oldPosition != verticalScrollPosition)
			{
				var scrollEvent:ScrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
				scrollEvent.direction = ScrollEventDirection.VERTICAL;
				scrollEvent.position = verticalScrollPosition;
				scrollEvent.delta = verticalScrollPosition - oldPosition;
				dispatchEvent(scrollEvent);
			}
        }
    }

	//--------------------------------------------------------------------------
	//
	//  Event handlers
	//
	//--------------------------------------------------------------------------

    /**
	 *  Handles CollectionEvents dispatched from the data provider
	 *  as the data changes.
	 *  Updates the renderers, selected indices and scrollbars as needed.
	 *
	 *  @param event The CollectionEvent.
     */
    protected function collectionChangeHandler(event:Event):void
    {
		var len:int;
		var i:int;
		var data:ListBaseSelectionData;
		var p:String;
		var selectedUID:String;

        if (event is CollectionEvent)
        {
            // trace("ListBase collectionEvent");
            var ce:CollectionEvent = CollectionEvent(event);
            
			if (ce.kind == CollectionEventKind.ADD)
            {
                // trace("ListBase collectionEvent ADD");
                // special case when we have less than a screen full of stuff
                if (ce.location == 0 && verticalScrollPosition == 0)
                {
					try
					{
						// trace("ListBase collectionEvent ADD adjust");
						iterator.seek(CursorBookmark.FIRST);
					}
					catch (e:ItemPendingError)
					{
						// trace("IPE in set horizontalScrollPosition");
						lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0)
						e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
															lastSeekPending));
						iteratorValid = false;
						// do nothing, we'll repaint when the data arrives
					}
                }
				else if (listType == "vertical" && verticalScrollPosition >= ce.location)
				{
					super.verticalScrollPosition = super.verticalScrollPosition + ce.items.length;
				}

				len = ce.items.length;
				for (p in selectedData)
				{
					data = selectedData[p];
					if (data.index > ce.location)
						data.index += len;
				}
				
				if (_selectedIndex > ce.location)
					_selectedIndex += len;
            }

            else if (ce.kind == CollectionEventKind.REPLACE)
            {
                // trace("ListBase collectionEvent REPLACE");
	            selectedUID = selectedItem ? itemToUID(selectedItem) : null;
				len = ce.items.length;
				for (i = 0; i < len; i++)
				{
					var oldUID:String = itemToUID(ce.items[i].oldValue);
					var sd:ListBaseSelectionData = selectedData[oldUID];
					if (sd)
					{
						sd.data = ce.items[i].newValue;
						delete selectedData[oldUID];
						selectedData[itemToUID(sd.data)] = sd;
					
						if (selectedUID == oldUID)
						{
							_selectedItem = sd.data;
							dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
						}
					}
				}
            }

            else if (ce.kind == CollectionEventKind.REMOVE)
            {
                // trace("ListBase collectionEvent REMOVE");
				// make sure we've generated rows for the actual data
				// at startup time we might just have blank rows
                if (listItems.length && listItems[lockedRowCount].length)
                {
	                // special case when we have less than a screen full of stuff
	                var firstUID:String = rowMap[listItems[lockedRowCount][0].name].uid;
	                selectedUID = selectedItem ? itemToUID(selectedItem) : null;
	                
					for (i = 0; i < ce.items.length; i++)
	                {
	                    var uid:String = itemToUID(ce.items[i]);
	                    
						if (uid == firstUID && verticalScrollPosition == 0)
	                        iterator.seek(CursorBookmark.FIRST);
	                    
						if (selectedData[uid])
	                        removeSelectionData(uid);
	                    
						if (selectedUID == uid)
	                    {
	                        _selectedItem = null;
							_selectedIndex = -1;
							dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
	                    }

	                    removeIndicators(uid);
	                }
					
					// Decrement verticalScrollPosition by the number of items that have
					// been removed from the top.
					if (listType == "vertical" && verticalScrollPosition > ce.location)
					{
						super.verticalScrollPosition = verticalScrollPosition -
							Math.min(ce.items.length,
							verticalScrollPosition - ce.location);
						
						iterator.seek(CursorBookmark.FIRST, verticalScrollPosition);
					}

					len = ce.items.length;
	                i = 0;
	                for (var s:String in selectedData)
	                {
	                    i++;
						data = selectedData[s];
						if (data.index > ce.location)
							data.index -= len;
	                }

					if (_selectedIndex > ce.location)
	            		_selectedIndex -= len;
					
					// selected the last thing if the selected item
					// got removed.
					if (i > 0 && _selectedIndex == -1)
					{
						_selectedIndex = data.index;
						_selectedItem = data.data;
						dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
					}

	                if (i == 0)
	                {
						_selectedIndex = -1;
				        bSelectionChanged = true;
	                    bSelectedIndexChanged = true;
	                    invalidateDisplayList();
	                }
                }
            }

            else if (ce.kind == CollectionEventKind.MOVE)
            {
				if (ce.oldLocation < ce.location)
				{
					for (p in selectedData)
					{
						data = selectedData[p];
						if (data.index > ce.oldLocation &&
							data.index < ce.location)
							data.index --;

						else if (data.index == ce.oldLocation)
							data.index = ce.location;
					}
					if (_selectedIndex > ce.oldLocation &&
						_selectedIndex < ce.location)
						_selectedIndex --;
					else if (_selectedIndex == ce.oldLocation)
						_selectedIndex = ce.location;
				}
				else if (ce.location < ce.oldLocation)
				{
					for (p in selectedData)
					{
						data = selectedData[p];
						if (data.index > ce.location &&
							data.index < ce.oldLocation)
							data.index ++;

						else if (data.index == ce.oldLocation)
							data.index = ce.location;
					}
					if (_selectedIndex > ce.location &&
						_selectedIndex < ce.oldLocation)
						_selectedIndex ++;
					else if (_selectedIndex == ce.oldLocation)
						_selectedIndex = ce.location;
				}

				// if the current item got moved
				if (ce.oldLocation == verticalScrollPosition)
				{
					// iterator is at new position, jump to it,
					// but make sure we don't max out first
					if (ce.location > maxVerticalScrollPosition)
					{
						iterator.seek(CursorBookmark.CURRENT, maxVerticalScrollPosition - ce.location);
					}
					super.verticalScrollPosition = Math.min(ce.location, maxVerticalScrollPosition);
				}
				// if the old location and new location are on
				// different sides of the scrollposition
                else if ((ce.location >= verticalScrollPosition) && 
					 (ce.oldLocation < verticalScrollPosition))
				{
					try
					{
						// trace("ListBase collectionEvent ADD adjust");
						iterator.moveNext();
					}
					catch (e:ItemPendingError)
					{
						// trace("IPE in set horizontalScrollPosition");
						lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, verticalScrollPosition)
						e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
															lastSeekPending));
						iteratorValid = false;
						// do nothing, we'll repaint when the data arrives
					}
				}
				else if ((ce.location <= verticalScrollPosition) && 
						 (ce.oldLocation > verticalScrollPosition))
				{
					try
					{
						// trace("ListBase collectionEvent ADD adjust");
						iterator.movePrevious();
					}
					catch (e:ItemPendingError)
					{
						// trace("IPE in set horizontalScrollPosition");
						lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, verticalScrollPosition)
						e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
															lastSeekPending));
						iteratorValid = false;
						// do nothing, we'll repaint when the data arrives
					}
				}
			}

            else if (ce.kind == CollectionEventKind.REFRESH)
            {
				if (anchorBookmark)
				{
					try
					{
						iterator.seek(anchorBookmark, 0);
					}
					catch (e:ItemPendingError)
					{
						bSortItemPending = true;

						e.addResponder(new ItemResponder(
							seekPendingResultHandler, seekPendingFailureHandler,
							new ListBaseSeekPending(anchorBookmark, 0)));
						
						// trace("IPE in UpdateDisplayList");
						iteratorValid = false;
					}
					catch (cursorError:CursorError)
					{
						// might have been filtered out so
						// clear selections
						clearSelected();
					}
					adjustAfterSort();
				}
				else
				{
					try
					{
						iterator.seek(CursorBookmark.FIRST,
									  verticalScrollPosition);
					}
					catch (e:ItemPendingError)
					{
						bSortItemPending = true;

						e.addResponder(new ItemResponder(
							seekPendingResultHandler, seekPendingFailureHandler,
							new ListBaseSeekPending(CursorBookmark.FIRST,
													verticalScrollPosition)));

						// trace("IPE in UpdateDisplayList");
						iteratorValid = false;
					}
				}
            }

            else if (ce.kind == CollectionEventKind.RESET)
            {
				try
				{
					iterator.seek(CursorBookmark.FIRST);
	                collectionIterator.seek(CursorBookmark.FIRST);
				}
				catch (e:ItemPendingError)
				{
					lastSeekPending =
						new ListBaseSeekPending(CursorBookmark.FIRST, 0);
					
					e.addResponder(new ItemResponder(
						seekPendingResultHandler, seekPendingFailureHandler,
						lastSeekPending));

					iteratorValid = false;
				}
 				
				if (bSelectedIndexChanged || bSelectedItemChanged ||
					bSelectedIndicesChanged || bSelectedItemsChanged)
				{
 					bSelectionChanged = true;
				}
   				else
				{
 					commitSelectedIndex(-1);
				}
				
				if (isNaN(verticalScrollPositionPending))
				{
					verticalScrollPositionPending = 0;
					super.verticalScrollPosition = 0;
				}
				
				if (isNaN(horizontalScrollPositionPending))
				{
					horizontalScrollPositionPending = 0;
					super.horizontalScrollPosition = 0;
				}
                
				invalidateSize();
            }
        }

        itemsSizeChanged = true;

        invalidateDisplayList();
    }

    /**
	 *  Handles <code>MouseEvent.MOUSE_OVER</code> events from any mouse
	 *  targets contained in the list, including the renderers.
	 *  This method finds out which renderer the mouse is over
	 *  and shows it as highlighted.
	 *
	 *  <p>The list classes also call this from a 
	 *  <code>MouseEvent.MOUSE_MOVE</code> event.
	 *  This event is used to detect movement in non-target areas of the
	 *  renderers and in padded areas around the renderers.</p>
	 *
	 *  @param event The mouse event
     */
    protected function mouseOverHandler(event:MouseEvent):void
    {
        if (!enabled || !selectable)
			return;

        isPressed = event.buttonDown;

		var item:IListItemRenderer = mouseEventToItemRenderer(event);
		if (!item)
			return;

		if (!isPressed || allowDragSelection)
        {
			// we're rolling onto different subpieces of ourself or our highlight indicator
			if (event.relatedObject)
			{
				if (itemRendererContains(item, event.relatedObject) ||
					item == lastHighlightItemRenderer ||
					event.relatedObject == highlightIndicator)
						return;
			}		

            if (getStyle("useRollOver") && (item.data != null))
            {
				if (allowDragSelection)
					bSelectOnRelease = true;
                
				var uid:String = itemToUID(item.data);
                drawItem(visibleData[uid], isItemSelected(item.data), true, uid == caretUID);
				var pt:Point = itemRendererToIndices(item);
                if (pt) // during tweens, we may get null
                {
                    var listEvent:ListEvent =
                        new ListEvent(ListEvent.ITEM_ROLL_OVER);
                    listEvent.columnIndex = pt.x;
                    listEvent.rowIndex = pt.y;
					listEvent.itemRenderer = item;
                    dispatchEvent(listEvent);
                }
            }
        }
        else
        {
            if (DragManager.isDragging)
                return;
            
			if ((dragScrollingInterval != 0 && allowDragSelection) || menuSelectionMode)
            {
                if (selectItem(item, event.shiftKey, event.ctrlKey))
					dispatchEvent(new Event("change"));
            }
        }
    }

    /**
	 *  Handles <code>MouseEvent.MOUSE_OUT</code> events from any mouse targets
	 *  contained in the list including the renderers.  This method
	 *  finds out which renderer the mouse has left
	 *  and removes the highlights.
	 *
	 *  @param event The mouse event
     */
    protected function mouseOutHandler(event:MouseEvent):void
    {
        if (!enabled || !selectable)
			return;

		isPressed = event.buttonDown;

        var item:IListItemRenderer = mouseEventToItemRenderer(event);
        if (!item)
			return;

        if (!isPressed)
        {
			// either we're rolling onto different subpieces of ourself or our 
			// highlight indicator, or the clearing of the highlighted item has 
			// already happened care of the mouseMove handler
			if (itemRendererContains(item, event.relatedObject) || 
				event.relatedObject == listContent || 
				event.relatedObject == highlightIndicator || 
				!highlightItemRenderer)
				return;

            if (getStyle("useRollOver") && item.data)
				clearHighlight(item);
        }
    }

	/**
	 *  Handles <code>MouseEvent.MOUSE_MOVE</code> events from any mouse
	 *  targets contained in the list including the renderers.  This method
	 *  watches for a gesture that constitutes the beginning of a
	 *  drag drop and send a <code>DragEvent.DRAG_START</code> event.
	 *  It also checks to see if the mouse is over a non-target area of a
	 *  renderer so that Flex can try to make it look like that renderer was 
	 *  the target.
	 *
	 *  @param event The mouse event
	 */
	protected function mouseMoveHandler(event:MouseEvent):void
	{
		if (!enabled || !selectable)
			return;

        var pt:Point = new Point(event.localX, event.localY);
        pt = DisplayObject(event.target).localToGlobal(pt);
        pt = globalToLocal(pt);


		if (isPressed && mouseDownPoint &&
			(Math.abs(mouseDownPoint.x - pt.x) > DRAG_THRESHOLD ||
			 Math.abs(mouseDownPoint.y - pt.y) > DRAG_THRESHOLD))
		{
        	if (dragEnabled && !DragManager.isDragging && mouseDownPoint)
        	{
				var dragEvent:DragEvent = new DragEvent(DragEvent.DRAG_START);
				dragEvent.dragInitiator = this;
				dragEvent.localX = mouseDownPoint.x;
				dragEvent.localY = mouseDownPoint.y;
				dragEvent.buttonDown = true;
				dispatchEvent(dragEvent);
        	}
        }

		// we used to put shields into each of the renderers so leftover space was hittable
		// but that's makes too many shields at startup and scrolling.  The gamble is that we
		// can run the code even on a large grid very quickly compared to mouse move intervals.
		var item:IListItemRenderer = mouseEventToItemRenderer(event);
		if (highlightItemRenderer && item != highlightItemRenderer)
		{
			if (!isPressed)
			{
				if (getStyle("useRollOver") && highlightItemRenderer.data)
				{
					clearHighlight(highlightItemRenderer)
				}
			}
		}
		if (item && !highlightItemRenderer)
		{
			mouseOverHandler(event);
		}
	}

    /**
	 *  Handles <code>MouseEvent.MOUSE_DOWN</code> events from any mouse
	 *  targets contained in the list including the renderers.  This method
	 *  finds the renderer that was pressed and prepares to receive
	 *  a <code>MouseEvent.MOUSE_UP</code> event.
	 *
	 *  @param event The mouse event
     */
    protected function mouseDownHandler(event:MouseEvent):void
    {
        if (!enabled || !selectable)
			return;

        // trace("mouseDown");
        isPressed = true;

        var item:IListItemRenderer = mouseEventToItemRenderer(event);
        if (!item)
			return;

        bSelectOnRelease = false;

        var pt:Point = new Point(event.localX, event.localY);
        pt = DisplayObject(event.target).localToGlobal(pt);
        mouseDownPoint = globalToLocal(pt);

		systemManager.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true);

		if (!dragEnabled)
		{
            dragScrollingInterval = setInterval(dragScroll, 15);
		}

        // If dragEnabled is true, clicks on selected contents should cause
        // a selection change on mouse up instead of mouse down. Otherwise,
        // clicking in a selection to drag will deselect any multiple selection
        // before the drag occurs.
        if (dragEnabled && selectedData[rowMap[item.name].uid])
            bSelectOnRelease = true;
        else
        {
            if (selectItem(item, event.shiftKey, event.ctrlKey))
				mouseDownItem = item;
        }
    }

    /**
	 *  Handles <code>MouseEvent.MOUSE_DOWN</code> events from any mouse
	 *  targets contained in the list including the renderers.  This method
	 *  finds the renderer that was pressed and prepares to receive
	 *  a <code>MouseEvent.MOUSE_UP</code> event.
	 *
	 *  @param event The mouse event
     */
    protected function mouseUpHandler(event:MouseEvent):void
    {
        mouseDownPoint = null;
        // trace("mouseUp");
        var item:IListItemRenderer = mouseEventToItemRenderer(event);

		systemManager.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true);

        if (!dragEnabled && dragScrollingInterval != 0)
        {
            clearInterval(dragScrollingInterval);
            dragScrollingInterval = 0;
        }

        if (!enabled || !selectable)
            return;

        if (!item || !hitTestPoint(event.stageX, event.stageY))
		{
	        isPressed = false;
			return;
		}

        if (bSelectOnRelease)
        {
            bSelectOnRelease = false;
            if (selectItem(item, event.shiftKey, event.ctrlKey))
	            dispatchEvent(new Event("change"));
        }

		if (mouseDownItem)
		{
            dispatchEvent(new Event("change"));
			mouseDownItem = null;
		}

        isPressed = false;
    }

    /**
	 *  Handles <code>MouseEvent.MOUSE_CLICK</code> events from any mouse
	 *  targets contained in the list including the renderers.  This method
	 *  determines which renderer was clicked
	 *  and dispatches a <code>ListEvent.ITEM_CLICK</code> event.
     */
    protected function mouseClickHandler(event:MouseEvent):void
    {
		var item:IListItemRenderer = mouseEventToItemRenderer(event);
		if (!item)
			return;

        var pt:Point = itemRendererToIndices(item);
        if (pt) // during tweens, we may get null
        {
            var listEvent:ListEvent =
                new ListEvent(ListEvent.ITEM_CLICK);
            listEvent.columnIndex = pt.x;
            listEvent.rowIndex = pt.y;
			listEvent.itemRenderer = item;
            dispatchEvent(listEvent);
        }
    }

    /**
	 *  Handles <code>MouseEvent.MOUSE_DOUBLE_CLICK</code> events from any
	 *  mouse targets contained in the list including the renderers.
	 *  This method determines which renderer was clicked
	 *  and dispatches a <code>ListEvent.ITEM_DOUBLE_CLICK</code> event.
     */
    protected function mouseDoubleClickHandler(event:MouseEvent):void
    {
		var item:IListItemRenderer = mouseEventToItemRenderer(event);
		if (!item)
			return;

        var pt:Point = itemRendererToIndices(item);
        if (pt) // during tweens, we may get null
        {
            var listEvent:ListEvent =
                new ListEvent(ListEvent.ITEM_DOUBLE_CLICK);
            listEvent.columnIndex = pt.x;
            listEvent.rowIndex = pt.y;
			listEvent.itemRenderer = item;
            dispatchEvent(listEvent);
        }
    }

    /**
     *  @private
     *  The default handler for the <code>dragStart</code> event.
     */
    private function dragStartHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;

        var dragSource:DragSource = new DragSource();

		addDragData(dragSource);

        var point:Point = dragImageOffsets;

		DragManager.doDrag(this, dragSource, event, dragImage,
						   -point.x, -point.y, 0.5, dragMoveEnabled);
    }

    /**
	 *  Handles <code>DragEvent.DRAG_ENTER</code> events.  This method
	 *  determines if the DragSource object contains valid elements and uses
	 *  the <code>showDropFeedback()</code> method to set up the UI feeback.
	 *
	 *  @param event The DragEvent
     */
    protected function dragEnterHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;

        if (event.dragSource.hasFormat("items"))
        {
            DragManager.acceptDragDrop(this);
            DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE);
            showDropFeedback(event);
            return;
        }

        hideDropFeedback(event);
        
		DragManager.showFeedback(DragManager.NONE);
    }

    /**
	 *  Handles <code>DragEvent.DRAG_OVER</code> events.  This method
	 *  determines if the DragSource object contains valid elements and uses
	 *  the <code>showDropFeedback()</code> method to set up the UI feeback.
	 *
	 *  @param event The DragEvent
     */
    protected function dragOverHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;

        if (event.dragSource.hasFormat("items"))
        {
            DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE);
            showDropFeedback(event);
            return;
        }

        hideDropFeedback(event);
        
		DragManager.showFeedback(DragManager.NONE);
    }

    /**
	 *  Handles <code>DragEvent.DRAG_EXIT</code> events.  This method hides
	 *  the UI feeback by calling the <code>hideDropFeedback()</code> method.
	 *
	 *  @param event The DragEvent
     */
    protected function dragExitHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;

        hideDropFeedback(event);
        
		DragManager.showFeedback(DragManager.NONE);
    }

    /**
	 *  Handles <code>DragEvent.DRAG_DROP events</code>.  This method  hides
	 *  the UI feeback by calling the <code>hideDropFeedback()</code> method.
	 *
	 *  @param event The DragEvent
     */
    protected function dragDropHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;

        hideDropFeedback(event);

        if (event.dragSource.hasFormat("items"))
        {
			if (!dataProvider)
				// Create an empty collection to drop items into.
				dataProvider = [];

            var items:Array = event.dragSource.dataForFormat("items") as Array;
            collectionIterator.seek(CursorBookmark.FIRST, calculateDropIndex());
            for (var i:int = items.length - 1; i >= 0; i--)
            {
                collectionIterator.insert(items[i]);
            }
        }
    }

    /**
	 *  Handles <code>DragEvent.DRAG_COMPLETE</code> events.  This method
	 *  removes the item from the data provider.
	 *
	 *  @param event The DragEvent
     */
    protected function dragCompleteHandler(event:DragEvent):void
    {
        isPressed = false;

        if (event.isDefaultPrevented())
            return;

        if (event.action == DragManager.MOVE && dragMoveEnabled)
        {
            var indices:Array = selectedIndices;
			indices.sort(Array.NUMERIC);
            var n:int = indices.length;
			for (var i:int = n - 1; i >= 0; i--)
            {
                collectionIterator.seek(CursorBookmark.FIRST, indices[i]);
                collectionIterator.remove();
            }
			clearSelected(false);
        }
    }

    /**
     *  @private
     */
    private function selectionTween_updateHandler(event:TweenEvent):void
	{
		Sprite(event.target.listener).alpha = Number(event.value);
	}

    /**
     *  @private
     */
	private function selectionTween_endHandler(event:TweenEvent):void
	{
		selectionTween_updateHandler(event);
	}
}

}
